Interrupt playback when new bluetooth audio output device is connected

Change-Id: I2bf08484e7912cb1a1a968b24394db2534544750
Signed-off-by: Eugene Mutavchi <Ievgen_Mutavchi@comcast.com>
diff --git a/src/third_party/starboard/rdk/shared/player/player_internal.cc b/src/third_party/starboard/rdk/shared/player/player_internal.cc
index 5fdc3fb..0236174 100644
--- a/src/third_party/starboard/rdk/shared/player/player_internal.cc
+++ b/src/third_party/starboard/rdk/shared/player/player_internal.cc
@@ -1113,6 +1113,7 @@
   GstElement* GetPipeline() const { return pipeline_;  }
   bool IsValid() const { return SbThreadIsValid(playback_thread_); }
   bool HasMaxVideoCaps() const { return !max_video_capabilities_.empty(); }
+  void AudioConfigurationChanged();
 
  private:
   enum class State {
@@ -1400,6 +1401,13 @@
     #endif
     return true;
   }
+
+  void AudioConfigurationChanged() {
+    ::starboard::ScopedLock lock(mutex_);
+    for(const auto& p: players_) {
+      p->AudioConfigurationChanged();
+    }
+  }
 };
 SB_ONCE_INITIALIZE_FUNCTION(PlayerRegistry, GetPlayerRegistry);
 
@@ -3069,6 +3077,15 @@
   }
 }
 
+void PlayerImpl::AudioConfigurationChanged() {
+  GST_WARNING_OBJECT(pipeline_, "Emitting an error on audio configuration change.");
+  DispatchOnWorkerThread(
+    new PlayerErrorTask(
+      player_error_func_, player_, context_,
+      kSbPlayerErrorCapabilityChanged, "Audio device capability changed"));
+
+}
+
 }  // namespace
 
 void ForceStop() {
@@ -3076,6 +3093,11 @@
   GetPlayerRegistry()->ForceStop();
 }
 
+void AudioConfigurationChanged() {
+  using third_party::starboard::rdk::shared::player::GetPlayerRegistry;
+  GetPlayerRegistry()->AudioConfigurationChanged();
+}
+
 }  // namespace player
 }  // namespace shared
 }  // namespace rdk
diff --git a/src/third_party/starboard/rdk/shared/player/player_internal.h b/src/third_party/starboard/rdk/shared/player/player_internal.h
index 2116198..845031f 100644
--- a/src/third_party/starboard/rdk/shared/player/player_internal.h
+++ b/src/third_party/starboard/rdk/shared/player/player_internal.h
@@ -45,6 +45,9 @@
   virtual void SetBounds(int zindex, int x, int y, int w, int h) = 0;
 };
 
+void ForceStop();
+void AudioConfigurationChanged();
+
 }  // namespace player
 }  // namespace shared
 }  // namespace rdk
@@ -75,6 +78,7 @@
     using third_party::starboard::rdk::shared::player::Player;
     return Player::MaxNumberOfSamplesPerWrite();
   }
+
   std::unique_ptr<third_party::starboard::rdk::shared::player::Player> player_;
 };
 
diff --git a/src/third_party/starboard/rdk/shared/rdkservices.cc b/src/third_party/starboard/rdk/shared/rdkservices.cc
index a18a9dd..a7061ae 100644
--- a/src/third_party/starboard/rdk/shared/rdkservices.cc
+++ b/src/third_party/starboard/rdk/shared/rdkservices.cc
@@ -52,6 +52,7 @@
 #include "third_party/starboard/rdk/shared/accessibility_data.h"
 #include "third_party/starboard/rdk/shared/log_override.h"
 #include "third_party/starboard/rdk/shared/application_rdk.h"
+#include "third_party/starboard/rdk/shared/player/player_internal.h"
 
 MODULE_NAME_DECLARATION(BUILD_REFERENCE);
 
@@ -1185,6 +1186,7 @@
       Add(_T("deviceType"), &Devicetype);
     }
   };
+
   struct ConnectedDevicesData : public Core::JSON::Container {
     ConnectedDevicesData()
       : Core::JSON::Container() {
@@ -1195,11 +1197,52 @@
     Core::JSON::ArrayType<DeviceDetailsData> Connecteddevices;
   };
 
-  void OnBluetoothStatusChanged(const Core::JSON::String&) { ForceNeedsRefresh(); }
+  struct StatusChangedData : public Core::JSON::Container {
+    StatusChangedData()
+      : Core::JSON::Container() {
+      Init();
+    }
+    StatusChangedData(const StatusChangedData& other)
+      : Core::JSON::Container()
+      , Name(other.Name)
+      , Newstatus(other.Newstatus)
+      , Devicetype(other.Devicetype)
+      , Connected(other.Connected) {
+      Init();
+    }
+    StatusChangedData& operator=(const StatusChangedData& rhs) {
+      Name = rhs.Name;
+      Devicetype = rhs.Devicetype;
+      Newstatus = rhs.Newstatus;
+      Connected = rhs.Connected;
+      return *this;
+    }
+    Core::JSON::String Name;
+    Core::JSON::String Newstatus;
+    Core::JSON::String Devicetype;
+    Core::JSON::Boolean Connected;
+  private:
+    void Init() {
+      Add(_T("name"), &Name);
+      Add(_T("newStatus"), &Newstatus);
+      Add(_T("deviceType"), &Devicetype);
+      Add(_T("connected"), &Connected);
+    }
+  };
+
+  void OnBluetoothStatusChanged(const StatusChangedData&);
   void Refresh();
   void ForceNeedsRefresh() {  needs_refresh_.store(true); }
   void InitAudioConfigurationForAudioPort(const std::string& port_name, SbMediaAudioConfiguration* out);
 
+  static bool IsAudioOutputDeviceType(const std::string& type) {
+    return (strcasestr(type.c_str(), "audio") ||
+            strcasestr(type.c_str(), "headset") ||
+            strcasestr(type.c_str(), "headphones") ||
+            strcasestr(type.c_str(), "loudspeaker") ||
+            strcasestr(type.c_str(), "handsfree"));
+  };
+
   ServiceLink device_info_ { kDeviceInfoCallsign };
   ServiceLink bluetooth_ { kBluetoothCallsign };
 
@@ -1241,6 +1284,39 @@
   return;
 }
 
+void DeviceInfoImpl::OnBluetoothStatusChanged(const StatusChangedData& data) {
+  const char kConnectionChange[] = "CONNECTION_CHANGE";
+
+  SB_LOG(INFO) << "Bluetooth status changed, new status: " << data.Newstatus.Value();
+
+  if (data.Newstatus.Value().compare(0, sizeof(kConnectionChange), kConnectionChange) != 0)
+    return;
+
+  if (!IsAudioOutputDeviceType(data.Devicetype.Value()))
+    return;
+
+  SB_LOG(INFO) << "Audio device change."
+               << " name: '" << data.Name.Value() << "',"
+               << " type: '" << data.Devicetype.Value() << "',"
+               << " connected: " << (data.Connected.Value() ? "yes" : "no");
+
+  const auto& hasBluetooth = [&]() -> bool {
+    ::starboard::ScopedLock lock(mutex_);
+    return std::find_if(audio_configurations_.begin(), audio_configurations_.end(), [](const SbMediaAudioConfiguration& cfg) {
+      return cfg.connector == kSbMediaAudioConnectorBluetooth;
+    }) != audio_configurations_.end();
+  };
+
+  ForceNeedsRefresh();
+
+  // Interrupt player only if new wireless device got connected
+  if (data.Connected.Value() && !hasBluetooth()) {
+    SbEventSchedule([](void*) {
+      player::AudioConfigurationChanged();
+    }, nullptr, 0);
+  }
+}
+
 void DeviceInfoImpl::Refresh() {
   if ( !needs_refresh_.load() || !needs_refresh_.exchange( false ) )
     return;
@@ -1252,7 +1328,7 @@
   VariableTimeout timeout { kDefaultTimeoutMs, 1000 };
 
   if (did_subscribe_.load() == false && did_subscribe_.exchange(true) == false) {
-    rc = bluetooth_.Subscribe<Core::JSON::String>(timeout.value(), "onStatusChanged", &DeviceInfoImpl::OnBluetoothStatusChanged, this);
+    rc = bluetooth_.Subscribe<StatusChangedData>(timeout.value(), "onStatusChanged", &DeviceInfoImpl::OnBluetoothStatusChanged, this);
     if (Core::ERROR_NONE != rc && Core::ERROR_DUPLICATE_KEY != rc) {
       SB_LOG(ERROR) << "Failed to subscribe to '" << kBluetoothCallsign
                     << ".onStatusChanged' event, rc=" << rc
@@ -1292,16 +1368,9 @@
   } else {
     auto index(connected_devices.Connecteddevices.Elements());
     while (index.Next()) {
-      const auto& isAudioOutputDeviceType = [](const std::string& type) -> bool {
-        return (strcasestr(type.c_str(), "audio") ||
-                strcasestr(type.c_str(), "headset") ||
-                strcasestr(type.c_str(), "headphones") ||
-                strcasestr(type.c_str(), "loudspeaker") ||
-                strcasestr(type.c_str(), "handsfree"));
-      };
       const auto& device_details = index.Current();
       SB_LOG(INFO) << "Bluetooth device name: " << device_details.Name.Value() << ", type: " << device_details.Devicetype.Value();
-      if (isAudioOutputDeviceType(device_details.Devicetype.Value())) {
+      if (IsAudioOutputDeviceType(device_details.Devicetype.Value())) {
         SbMediaAudioConfiguration configuration;
         InitAudioConfigurationForAudioPort("bluetooth", &configuration);
         audio_configs.push_back(std::move(configuration));