Home | History | Annotate | Download | only in media
      1 // Copyright (c) 2013 The Chromium Authors. All rights reserved.
      2 // Use of this source code is governed by a BSD-style license that can be
      3 // found in the LICENSE file.
      4 
      5 #include "content/browser/media/webrtc_internals.h"
      6 
      7 #include "base/path_service.h"
      8 #include "base/strings/string_number_conversions.h"
      9 #include "content/browser/media/webrtc_internals_ui_observer.h"
     10 #include "content/browser/web_contents/web_contents_view.h"
     11 #include "content/public/browser/browser_thread.h"
     12 #include "content/public/browser/content_browser_client.h"
     13 #include "content/public/browser/notification_service.h"
     14 #include "content/public/browser/notification_types.h"
     15 #include "content/public/browser/power_save_blocker.h"
     16 #include "content/public/browser/render_process_host.h"
     17 #include "content/public/browser/web_contents.h"
     18 
     19 using base::ProcessId;
     20 using std::string;
     21 
     22 namespace content {
     23 
     24 namespace {
     25 
     26 static base::LazyInstance<WebRTCInternals>::Leaky g_webrtc_internals =
     27     LAZY_INSTANCE_INITIALIZER;
     28 
     29 // Makes sure that |dict| has a ListValue under path "log".
     30 static base::ListValue* EnsureLogList(base::DictionaryValue* dict) {
     31   base::ListValue* log = NULL;
     32   if (!dict->GetList("log", &log)) {
     33     log = new base::ListValue();
     34     if (log)
     35       dict->Set("log", log);
     36   }
     37   return log;
     38 }
     39 
     40 }  // namespace
     41 
     42 WebRTCInternals::WebRTCInternals()
     43     : aec_dump_enabled_(false) {
     44   registrar_.Add(this, NOTIFICATION_RENDERER_PROCESS_TERMINATED,
     45                  NotificationService::AllBrowserContextsAndSources());
     46 // TODO(grunell): Shouldn't all the webrtc_internals* files be excluded from the
     47 // build if WebRTC is disabled?
     48 #if defined(ENABLE_WEBRTC)
     49   aec_dump_file_path_ =
     50       GetContentClient()->browser()->GetDefaultDownloadDirectory();
     51   if (aec_dump_file_path_.empty()) {
     52     // In this case the default path (|aec_dump_file_path_|) will be empty and
     53     // the platform default path will be used in the file dialog (with no
     54     // default file name). See SelectFileDialog::SelectFile. On Android where
     55     // there's no dialog we'll fail to open the file.
     56     VLOG(1) << "Could not get the download directory.";
     57   } else {
     58     aec_dump_file_path_ =
     59         aec_dump_file_path_.Append(FILE_PATH_LITERAL("audio.aecdump"));
     60   }
     61 #endif  // defined(ENABLE_WEBRTC)
     62 }
     63 
     64 WebRTCInternals::~WebRTCInternals() {
     65 }
     66 
     67 WebRTCInternals* WebRTCInternals::GetInstance() {
     68   return g_webrtc_internals.Pointer();
     69 }
     70 
     71 void WebRTCInternals::OnAddPeerConnection(int render_process_id,
     72                                           ProcessId pid,
     73                                           int lid,
     74                                           const string& url,
     75                                           const string& rtc_configuration,
     76                                           const string& constraints) {
     77   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
     78 
     79   base::DictionaryValue* dict = new base::DictionaryValue();
     80   if (!dict)
     81     return;
     82 
     83   dict->SetInteger("rid", render_process_id);
     84   dict->SetInteger("pid", static_cast<int>(pid));
     85   dict->SetInteger("lid", lid);
     86   dict->SetString("rtcConfiguration", rtc_configuration);
     87   dict->SetString("constraints", constraints);
     88   dict->SetString("url", url);
     89   peer_connection_data_.Append(dict);
     90   CreateOrReleasePowerSaveBlocker();
     91 
     92   if (observers_.might_have_observers())
     93     SendUpdate("addPeerConnection", dict);
     94 }
     95 
     96 void WebRTCInternals::OnRemovePeerConnection(ProcessId pid, int lid) {
     97   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
     98   for (size_t i = 0; i < peer_connection_data_.GetSize(); ++i) {
     99     base::DictionaryValue* dict = NULL;
    100     peer_connection_data_.GetDictionary(i, &dict);
    101 
    102     int this_pid = 0;
    103     int this_lid = 0;
    104     dict->GetInteger("pid", &this_pid);
    105     dict->GetInteger("lid", &this_lid);
    106 
    107     if (this_pid != static_cast<int>(pid) || this_lid != lid)
    108       continue;
    109 
    110     peer_connection_data_.Remove(i, NULL);
    111     CreateOrReleasePowerSaveBlocker();
    112 
    113     if (observers_.might_have_observers()) {
    114       base::DictionaryValue id;
    115       id.SetInteger("pid", static_cast<int>(pid));
    116       id.SetInteger("lid", lid);
    117       SendUpdate("removePeerConnection", &id);
    118     }
    119     break;
    120   }
    121 }
    122 
    123 void WebRTCInternals::OnUpdatePeerConnection(
    124     ProcessId pid, int lid, const string& type, const string& value) {
    125   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
    126 
    127   for (size_t i = 0; i < peer_connection_data_.GetSize(); ++i) {
    128     base::DictionaryValue* record = NULL;
    129     peer_connection_data_.GetDictionary(i, &record);
    130 
    131     int this_pid = 0, this_lid = 0;
    132     record->GetInteger("pid", &this_pid);
    133     record->GetInteger("lid", &this_lid);
    134 
    135     if (this_pid != static_cast<int>(pid) || this_lid != lid)
    136       continue;
    137 
    138     // Append the update to the end of the log.
    139     base::ListValue* log = EnsureLogList(record);
    140     if (!log)
    141       return;
    142 
    143     base::DictionaryValue* log_entry = new base::DictionaryValue();
    144     if (!log_entry)
    145       return;
    146 
    147     double epoch_time = base::Time::Now().ToJsTime();
    148     string time = base::DoubleToString(epoch_time);
    149     log_entry->SetString("time", time);
    150     log_entry->SetString("type", type);
    151     log_entry->SetString("value", value);
    152     log->Append(log_entry);
    153 
    154     if (observers_.might_have_observers()) {
    155       base::DictionaryValue update;
    156       update.SetInteger("pid", static_cast<int>(pid));
    157       update.SetInteger("lid", lid);
    158       update.MergeDictionary(log_entry);
    159 
    160       SendUpdate("updatePeerConnection", &update);
    161     }
    162     return;
    163   }
    164 }
    165 
    166 void WebRTCInternals::OnAddStats(base::ProcessId pid, int lid,
    167                                  const base::ListValue& value) {
    168   if (!observers_.might_have_observers())
    169     return;
    170 
    171   base::DictionaryValue dict;
    172   dict.SetInteger("pid", static_cast<int>(pid));
    173   dict.SetInteger("lid", lid);
    174 
    175   base::ListValue* list = value.DeepCopy();
    176   if (!list)
    177     return;
    178 
    179   dict.Set("reports", list);
    180 
    181   SendUpdate("addStats", &dict);
    182 }
    183 
    184 void WebRTCInternals::OnGetUserMedia(int rid,
    185                                      base::ProcessId pid,
    186                                      const std::string& origin,
    187                                      bool audio,
    188                                      bool video,
    189                                      const std::string& audio_constraints,
    190                                      const std::string& video_constraints) {
    191   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
    192 
    193   base::DictionaryValue* dict = new base::DictionaryValue();
    194   dict->SetInteger("rid", rid);
    195   dict->SetInteger("pid", static_cast<int>(pid));
    196   dict->SetString("origin", origin);
    197   if (audio)
    198     dict->SetString("audio", audio_constraints);
    199   if (video)
    200     dict->SetString("video", video_constraints);
    201 
    202   get_user_media_requests_.Append(dict);
    203 
    204   if (observers_.might_have_observers())
    205     SendUpdate("addGetUserMedia", dict);
    206 }
    207 
    208 void WebRTCInternals::AddObserver(WebRTCInternalsUIObserver *observer) {
    209   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
    210   observers_.AddObserver(observer);
    211 }
    212 
    213 void WebRTCInternals::RemoveObserver(WebRTCInternalsUIObserver *observer) {
    214   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
    215   observers_.RemoveObserver(observer);
    216 
    217   // Disables the AEC recording if it is enabled and the last webrtc-internals
    218   // page is going away.
    219   if (aec_dump_enabled_ && !observers_.might_have_observers())
    220     DisableAecDump();
    221 }
    222 
    223 void WebRTCInternals::UpdateObserver(WebRTCInternalsUIObserver* observer) {
    224   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
    225   if (peer_connection_data_.GetSize() > 0)
    226     observer->OnUpdate("updateAllPeerConnections", &peer_connection_data_);
    227 
    228   for (base::ListValue::iterator it = get_user_media_requests_.begin();
    229        it != get_user_media_requests_.end();
    230        ++it) {
    231     observer->OnUpdate("addGetUserMedia", *it);
    232   }
    233 }
    234 
    235 void WebRTCInternals::EnableAecDump(content::WebContents* web_contents) {
    236 #if defined(ENABLE_WEBRTC)
    237 #if defined(OS_ANDROID)
    238   EnableAecDumpOnAllRenderProcessHosts();
    239 #else
    240   select_file_dialog_ = ui::SelectFileDialog::Create(this, NULL);
    241   select_file_dialog_->SelectFile(
    242       ui::SelectFileDialog::SELECT_SAVEAS_FILE,
    243       base::string16(),
    244       aec_dump_file_path_,
    245       NULL,
    246       0,
    247       FILE_PATH_LITERAL(""),
    248       web_contents->GetTopLevelNativeWindow(),
    249       NULL);
    250 #endif
    251 #endif
    252 }
    253 
    254 void WebRTCInternals::DisableAecDump() {
    255 #if defined(ENABLE_WEBRTC)
    256   aec_dump_enabled_ = false;
    257 
    258   // Tear down the dialog since the user has unchecked the AEC dump box.
    259   select_file_dialog_ = NULL;
    260 
    261   for (RenderProcessHost::iterator i(
    262            content::RenderProcessHost::AllHostsIterator());
    263        !i.IsAtEnd(); i.Advance()) {
    264     i.GetCurrentValue()->DisableAecDump();
    265   }
    266 #endif
    267 }
    268 
    269 void WebRTCInternals::ResetForTesting() {
    270   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
    271   observers_.Clear();
    272   peer_connection_data_.Clear();
    273   CreateOrReleasePowerSaveBlocker();
    274   get_user_media_requests_.Clear();
    275   aec_dump_enabled_ = false;
    276 }
    277 
    278 void WebRTCInternals::SendUpdate(const string& command, base::Value* value) {
    279   DCHECK(observers_.might_have_observers());
    280 
    281   FOR_EACH_OBSERVER(WebRTCInternalsUIObserver,
    282                     observers_,
    283                     OnUpdate(command, value));
    284 }
    285 
    286 void WebRTCInternals::Observe(int type,
    287                               const NotificationSource& source,
    288                               const NotificationDetails& details) {
    289   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
    290   DCHECK_EQ(type, NOTIFICATION_RENDERER_PROCESS_TERMINATED);
    291   OnRendererExit(Source<RenderProcessHost>(source)->GetID());
    292 }
    293 
    294 void WebRTCInternals::FileSelected(const base::FilePath& path,
    295                                    int /* unused_index */,
    296                                    void* /*unused_params */) {
    297 #if defined(ENABLE_WEBRTC)
    298   aec_dump_file_path_ = path;
    299   EnableAecDumpOnAllRenderProcessHosts();
    300 #endif
    301 }
    302 
    303 void WebRTCInternals::FileSelectionCanceled(void* params) {
    304 #if defined(ENABLE_WEBRTC)
    305   SendUpdate("aecRecordingFileSelectionCancelled", NULL);
    306 #endif
    307 }
    308 
    309 void WebRTCInternals::OnRendererExit(int render_process_id) {
    310   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
    311 
    312   // Iterates from the end of the list to remove the PeerConnections created
    313   // by the exitting renderer.
    314   for (int i = peer_connection_data_.GetSize() - 1; i >= 0; --i) {
    315     base::DictionaryValue* record = NULL;
    316     peer_connection_data_.GetDictionary(i, &record);
    317 
    318     int this_rid = 0;
    319     record->GetInteger("rid", &this_rid);
    320 
    321     if (this_rid == render_process_id) {
    322       if (observers_.might_have_observers()) {
    323         int lid = 0, pid = 0;
    324         record->GetInteger("lid", &lid);
    325         record->GetInteger("pid", &pid);
    326 
    327         base::DictionaryValue update;
    328         update.SetInteger("lid", lid);
    329         update.SetInteger("pid", pid);
    330         SendUpdate("removePeerConnection", &update);
    331       }
    332       peer_connection_data_.Remove(i, NULL);
    333     }
    334   }
    335   CreateOrReleasePowerSaveBlocker();
    336 
    337   bool found_any = false;
    338   // Iterates from the end of the list to remove the getUserMedia requests
    339   // created by the exiting renderer.
    340   for (int i = get_user_media_requests_.GetSize() - 1; i >= 0; --i) {
    341     base::DictionaryValue* record = NULL;
    342     get_user_media_requests_.GetDictionary(i, &record);
    343 
    344     int this_rid = 0;
    345     record->GetInteger("rid", &this_rid);
    346 
    347     if (this_rid == render_process_id) {
    348       get_user_media_requests_.Remove(i, NULL);
    349       found_any = true;
    350     }
    351   }
    352 
    353   if (found_any && observers_.might_have_observers()) {
    354     base::DictionaryValue update;
    355     update.SetInteger("rid", render_process_id);
    356     SendUpdate("removeGetUserMediaForRenderer", &update);
    357   }
    358 }
    359 
    360 #if defined(ENABLE_WEBRTC)
    361 void WebRTCInternals::EnableAecDumpOnAllRenderProcessHosts() {
    362   aec_dump_enabled_ = true;
    363   for (RenderProcessHost::iterator i(
    364            content::RenderProcessHost::AllHostsIterator());
    365        !i.IsAtEnd(); i.Advance()) {
    366     i.GetCurrentValue()->EnableAecDump(aec_dump_file_path_);
    367   }
    368 }
    369 #endif
    370 
    371 void WebRTCInternals::CreateOrReleasePowerSaveBlocker() {
    372   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
    373 
    374   if (peer_connection_data_.empty() && power_save_blocker_) {
    375     DVLOG(1) << ("Releasing the block on application suspension since no "
    376                  "PeerConnections are active anymore.");
    377     power_save_blocker_.reset();
    378   } else if (!peer_connection_data_.empty() && !power_save_blocker_) {
    379     DVLOG(1) << ("Preventing the application from being suspended while one or "
    380                  "more PeerConnections are active.");
    381     power_save_blocker_ = content::PowerSaveBlocker::Create(
    382         content::PowerSaveBlocker::kPowerSaveBlockPreventAppSuspension,
    383         "WebRTC has active PeerConnections.").Pass();
    384   }
    385 }
    386 
    387 }  // namespace content
    388