Home | History | Annotate | Download | only in media
      1 // Copyright (c) 2012 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 "base/memory/scoped_ptr.h"
      6 #include "base/message_loop/message_loop.h"
      7 #include "base/strings/utf_string_conversions.h"
      8 #include "content/child/child_process.h"
      9 #include "content/renderer/media/media_stream.h"
     10 #include "content/renderer/media/media_stream_impl.h"
     11 #include "content/renderer/media/media_stream_track.h"
     12 #include "content/renderer/media/mock_media_stream_dispatcher.h"
     13 #include "content/renderer/media/mock_media_stream_video_source.h"
     14 #include "content/renderer/media/webrtc/mock_peer_connection_dependency_factory.h"
     15 #include "testing/gtest/include/gtest/gtest.h"
     16 #include "third_party/WebKit/public/platform/WebMediaDeviceInfo.h"
     17 #include "third_party/WebKit/public/platform/WebMediaStream.h"
     18 #include "third_party/WebKit/public/platform/WebMediaStreamSource.h"
     19 #include "third_party/WebKit/public/platform/WebMediaStreamTrack.h"
     20 #include "third_party/WebKit/public/platform/WebString.h"
     21 #include "third_party/WebKit/public/platform/WebVector.h"
     22 
     23 namespace content {
     24 
     25 class MockMediaStreamVideoCapturerSource : public MockMediaStreamVideoSource {
     26  public:
     27   MockMediaStreamVideoCapturerSource(
     28       const StreamDeviceInfo& device,
     29       const SourceStoppedCallback& stop_callback,
     30       PeerConnectionDependencyFactory* factory)
     31   : MockMediaStreamVideoSource(false) {
     32     SetDeviceInfo(device);
     33     SetStopCallback(stop_callback);
     34   }
     35 };
     36 
     37 class MediaStreamImplUnderTest : public MediaStreamImpl {
     38  public:
     39   enum RequestState {
     40     REQUEST_NOT_STARTED,
     41     REQUEST_NOT_COMPLETE,
     42     REQUEST_SUCCEEDED,
     43     REQUEST_FAILED,
     44   };
     45 
     46   MediaStreamImplUnderTest(MediaStreamDispatcher* media_stream_dispatcher,
     47                            PeerConnectionDependencyFactory* dependency_factory)
     48       : MediaStreamImpl(NULL, media_stream_dispatcher, dependency_factory),
     49         state_(REQUEST_NOT_STARTED),
     50         result_(NUM_MEDIA_REQUEST_RESULTS),
     51         factory_(dependency_factory),
     52         video_source_(NULL) {
     53   }
     54 
     55   void RequestUserMedia() {
     56     blink::WebUserMediaRequest user_media_request;
     57     state_ = REQUEST_NOT_COMPLETE;
     58     requestUserMedia(user_media_request);
     59   }
     60 
     61   void RequestMediaDevices() {
     62     blink::WebMediaDevicesRequest media_devices_request;
     63     state_ = REQUEST_NOT_COMPLETE;
     64     requestMediaDevices(media_devices_request);
     65   }
     66 
     67   virtual void GetUserMediaRequestSucceeded(
     68       const blink::WebMediaStream& stream,
     69       blink::WebUserMediaRequest* request_info) OVERRIDE {
     70     last_generated_stream_ = stream;
     71     state_ = REQUEST_SUCCEEDED;
     72   }
     73 
     74   virtual void GetUserMediaRequestFailed(
     75       blink::WebUserMediaRequest* request_info,
     76       content::MediaStreamRequestResult result) OVERRIDE {
     77     last_generated_stream_.reset();
     78     state_ = REQUEST_FAILED;
     79     result_ = result;
     80   }
     81 
     82   virtual void EnumerateDevicesSucceded(
     83       blink::WebMediaDevicesRequest* request,
     84       blink::WebVector<blink::WebMediaDeviceInfo>& devices) OVERRIDE {
     85     state_ = REQUEST_SUCCEEDED;
     86     last_devices_ = devices;
     87   }
     88 
     89   virtual MediaStreamVideoSource* CreateVideoSource(
     90       const StreamDeviceInfo& device,
     91       const MediaStreamSource::SourceStoppedCallback& stop_callback) OVERRIDE {
     92     video_source_ = new MockMediaStreamVideoCapturerSource(device,
     93                                                            stop_callback,
     94                                                            factory_);
     95     return video_source_;
     96   }
     97 
     98   const blink::WebMediaStream& last_generated_stream() {
     99     return last_generated_stream_;
    100   }
    101 
    102   const blink::WebVector<blink::WebMediaDeviceInfo>& last_devices() {
    103     return last_devices_;
    104   }
    105 
    106   void ClearLastGeneratedStream() {
    107     last_generated_stream_.reset();
    108   }
    109 
    110   MockMediaStreamVideoCapturerSource* last_created_video_source() const {
    111     return video_source_;
    112   }
    113 
    114   RequestState request_state() const { return state_; }
    115   content::MediaStreamRequestResult error_reason() const { return result_; }
    116 
    117  private:
    118   blink::WebMediaStream last_generated_stream_;
    119   RequestState state_;
    120   content::MediaStreamRequestResult result_;
    121   blink::WebVector<blink::WebMediaDeviceInfo> last_devices_;
    122   PeerConnectionDependencyFactory* factory_;
    123   MockMediaStreamVideoCapturerSource* video_source_;
    124 };
    125 
    126 class MediaStreamImplTest : public ::testing::Test {
    127  public:
    128   virtual void SetUp() {
    129     // Create our test object.
    130     child_process_.reset(new ChildProcess());
    131     ms_dispatcher_.reset(new MockMediaStreamDispatcher());
    132     dependency_factory_.reset(new MockPeerConnectionDependencyFactory());
    133     ms_impl_.reset(new MediaStreamImplUnderTest(ms_dispatcher_.get(),
    134                                                 dependency_factory_.get()));
    135   }
    136 
    137   blink::WebMediaStream RequestLocalMediaStream() {
    138     ms_impl_->RequestUserMedia();
    139     FakeMediaStreamDispatcherRequestUserMediaComplete();
    140     StartMockedVideoSource();
    141 
    142     EXPECT_EQ(MediaStreamImplUnderTest::REQUEST_SUCCEEDED,
    143               ms_impl_->request_state());
    144 
    145     blink::WebMediaStream desc = ms_impl_->last_generated_stream();
    146     content::MediaStream* native_stream =
    147         content::MediaStream::GetMediaStream(desc);
    148     if (!native_stream) {
    149       ADD_FAILURE();
    150       return desc;
    151     }
    152 
    153     blink::WebVector<blink::WebMediaStreamTrack> audio_tracks;
    154     desc.audioTracks(audio_tracks);
    155     blink::WebVector<blink::WebMediaStreamTrack> video_tracks;
    156     desc.videoTracks(video_tracks);
    157 
    158     EXPECT_EQ(1u, audio_tracks.size());
    159     EXPECT_EQ(1u, video_tracks.size());
    160     EXPECT_NE(audio_tracks[0].id(), video_tracks[0].id());
    161     return desc;
    162   }
    163 
    164   void FakeMediaStreamDispatcherRequestUserMediaComplete() {
    165     // Audio request ID is used as the shared request ID.
    166     ms_impl_->OnStreamGenerated(ms_dispatcher_->audio_input_request_id(),
    167                                 ms_dispatcher_->stream_label(),
    168                                 ms_dispatcher_->audio_input_array(),
    169                                 ms_dispatcher_->video_array());
    170   }
    171 
    172   void FakeMediaStreamDispatcherRequestMediaDevicesComplete() {
    173     ms_impl_->OnDevicesEnumerated(ms_dispatcher_->audio_input_request_id(),
    174                                   ms_dispatcher_->audio_input_array());
    175     ms_impl_->OnDevicesEnumerated(ms_dispatcher_->audio_output_request_id(),
    176                                   ms_dispatcher_->audio_output_array());
    177     ms_impl_->OnDevicesEnumerated(ms_dispatcher_->video_request_id(),
    178                                   ms_dispatcher_->video_array());
    179   }
    180 
    181   void StartMockedVideoSource() {
    182     MockMediaStreamVideoCapturerSource* video_source =
    183         ms_impl_->last_created_video_source();
    184     if (video_source->SourceHasAttemptedToStart())
    185       video_source->StartMockedSource();
    186   }
    187 
    188   void FailToStartMockedVideoSource() {
    189     MockMediaStreamVideoCapturerSource* video_source =
    190         ms_impl_->last_created_video_source();
    191     if (video_source->SourceHasAttemptedToStart())
    192       video_source->FailToStartMockedSource();
    193   }
    194 
    195   void FailToCreateNextAudioCapturer() {
    196     dependency_factory_->FailToCreateNextAudioCapturer();
    197   }
    198 
    199  protected:
    200   base::MessageLoop message_loop_;
    201   scoped_ptr<ChildProcess> child_process_;
    202   scoped_ptr<MockMediaStreamDispatcher> ms_dispatcher_;
    203   scoped_ptr<MediaStreamImplUnderTest> ms_impl_;
    204   scoped_ptr<MockPeerConnectionDependencyFactory> dependency_factory_;
    205 };
    206 
    207 TEST_F(MediaStreamImplTest, GenerateMediaStream) {
    208   // Generate a stream with both audio and video.
    209   blink::WebMediaStream mixed_desc = RequestLocalMediaStream();
    210 }
    211 
    212 // Test that the same source object is used if two MediaStreams are generated
    213 // using the same source.
    214 TEST_F(MediaStreamImplTest, GenerateTwoMediaStreamsWithSameSource) {
    215   blink::WebMediaStream desc1 = RequestLocalMediaStream();
    216   blink::WebMediaStream desc2 = RequestLocalMediaStream();
    217 
    218   blink::WebVector<blink::WebMediaStreamTrack> desc1_video_tracks;
    219   desc1.videoTracks(desc1_video_tracks);
    220   blink::WebVector<blink::WebMediaStreamTrack> desc2_video_tracks;
    221   desc2.videoTracks(desc2_video_tracks);
    222   EXPECT_EQ(desc1_video_tracks[0].source().id(),
    223             desc2_video_tracks[0].source().id());
    224 
    225   EXPECT_EQ(desc1_video_tracks[0].source().extraData(),
    226             desc2_video_tracks[0].source().extraData());
    227 
    228   blink::WebVector<blink::WebMediaStreamTrack> desc1_audio_tracks;
    229   desc1.audioTracks(desc1_audio_tracks);
    230   blink::WebVector<blink::WebMediaStreamTrack> desc2_audio_tracks;
    231   desc2.audioTracks(desc2_audio_tracks);
    232   EXPECT_EQ(desc1_audio_tracks[0].source().id(),
    233             desc2_audio_tracks[0].source().id());
    234 
    235   EXPECT_EQ(desc1_audio_tracks[0].source().extraData(),
    236             desc2_audio_tracks[0].source().extraData());
    237 }
    238 
    239 // Test that the same source object is not used if two MediaStreams are
    240 // generated using different sources.
    241 TEST_F(MediaStreamImplTest, GenerateTwoMediaStreamsWithDifferentSources) {
    242   blink::WebMediaStream desc1 = RequestLocalMediaStream();
    243   // Make sure another device is selected (another |session_id|) in  the next
    244   // gUM request.
    245   ms_dispatcher_->IncrementSessionId();
    246   blink::WebMediaStream desc2 = RequestLocalMediaStream();
    247 
    248   blink::WebVector<blink::WebMediaStreamTrack> desc1_video_tracks;
    249   desc1.videoTracks(desc1_video_tracks);
    250   blink::WebVector<blink::WebMediaStreamTrack> desc2_video_tracks;
    251   desc2.videoTracks(desc2_video_tracks);
    252   EXPECT_NE(desc1_video_tracks[0].source().id(),
    253             desc2_video_tracks[0].source().id());
    254 
    255   EXPECT_NE(desc1_video_tracks[0].source().extraData(),
    256             desc2_video_tracks[0].source().extraData());
    257 
    258   blink::WebVector<blink::WebMediaStreamTrack> desc1_audio_tracks;
    259   desc1.audioTracks(desc1_audio_tracks);
    260   blink::WebVector<blink::WebMediaStreamTrack> desc2_audio_tracks;
    261   desc2.audioTracks(desc2_audio_tracks);
    262   EXPECT_NE(desc1_audio_tracks[0].source().id(),
    263             desc2_audio_tracks[0].source().id());
    264 
    265   EXPECT_NE(desc1_audio_tracks[0].source().extraData(),
    266             desc2_audio_tracks[0].source().extraData());
    267 }
    268 
    269 TEST_F(MediaStreamImplTest, StopLocalTracks) {
    270   // Generate a stream with both audio and video.
    271   blink::WebMediaStream mixed_desc = RequestLocalMediaStream();
    272 
    273   blink::WebVector<blink::WebMediaStreamTrack> audio_tracks;
    274   mixed_desc.audioTracks(audio_tracks);
    275   MediaStreamTrack* audio_track = MediaStreamTrack::GetTrack(audio_tracks[0]);
    276   audio_track->Stop();
    277   EXPECT_EQ(1, ms_dispatcher_->stop_audio_device_counter());
    278 
    279   blink::WebVector<blink::WebMediaStreamTrack> video_tracks;
    280   mixed_desc.videoTracks(video_tracks);
    281   MediaStreamTrack* video_track = MediaStreamTrack::GetTrack(video_tracks[0]);
    282   video_track->Stop();
    283   EXPECT_EQ(1, ms_dispatcher_->stop_video_device_counter());
    284 }
    285 
    286 // This test that a source is not stopped even if the tracks in a
    287 // MediaStream is stopped if there are two MediaStreams with tracks using the
    288 // same device. The source is stopped
    289 // if there are no more MediaStream tracks using the device.
    290 TEST_F(MediaStreamImplTest, StopLocalTracksWhenTwoStreamUseSameDevices) {
    291   // Generate a stream with both audio and video.
    292   blink::WebMediaStream desc1 = RequestLocalMediaStream();
    293   blink::WebMediaStream desc2 = RequestLocalMediaStream();
    294 
    295   blink::WebVector<blink::WebMediaStreamTrack> audio_tracks1;
    296   desc1.audioTracks(audio_tracks1);
    297   MediaStreamTrack* audio_track1 = MediaStreamTrack::GetTrack(audio_tracks1[0]);
    298   audio_track1->Stop();
    299   EXPECT_EQ(0, ms_dispatcher_->stop_audio_device_counter());
    300 
    301   blink::WebVector<blink::WebMediaStreamTrack> audio_tracks2;
    302   desc2.audioTracks(audio_tracks2);
    303   MediaStreamTrack* audio_track2 = MediaStreamTrack::GetTrack(audio_tracks2[0]);
    304   audio_track2->Stop();
    305   EXPECT_EQ(1, ms_dispatcher_->stop_audio_device_counter());
    306 
    307   blink::WebVector<blink::WebMediaStreamTrack> video_tracks1;
    308   desc1.videoTracks(video_tracks1);
    309   MediaStreamTrack* video_track1 = MediaStreamTrack::GetTrack(video_tracks1[0]);
    310   video_track1->Stop();
    311   EXPECT_EQ(0, ms_dispatcher_->stop_video_device_counter());
    312 
    313   blink::WebVector<blink::WebMediaStreamTrack> video_tracks2;
    314   desc2.videoTracks(video_tracks2);
    315   MediaStreamTrack* video_track2 = MediaStreamTrack::GetTrack(video_tracks2[0]);
    316   video_track2->Stop();
    317   EXPECT_EQ(1, ms_dispatcher_->stop_video_device_counter());
    318 }
    319 
    320 TEST_F(MediaStreamImplTest, StopSourceWhenMediaStreamGoesOutOfScope) {
    321   // Generate a stream with both audio and video.
    322   RequestLocalMediaStream();
    323   // Makes sure the test itself don't hold a reference to the created
    324   // MediaStream.
    325   ms_impl_->ClearLastGeneratedStream();
    326 
    327   // Expect the sources to be stopped when the MediaStream goes out of scope.
    328   EXPECT_EQ(1, ms_dispatcher_->stop_audio_device_counter());
    329   EXPECT_EQ(1, ms_dispatcher_->stop_video_device_counter());
    330 }
    331 
    332 // Test that the MediaStreams are deleted if the owning WebFrame is deleted.
    333 // In the unit test the owning frame is NULL.
    334 TEST_F(MediaStreamImplTest, FrameWillClose) {
    335   // Test a stream with both audio and video.
    336   blink::WebMediaStream mixed_desc = RequestLocalMediaStream();
    337   blink::WebMediaStream desc2 = RequestLocalMediaStream();
    338 
    339   // Test that the MediaStreams are deleted if the owning WebFrame is deleted.
    340   // In the unit test the owning frame is NULL.
    341   ms_impl_->FrameWillClose(NULL);
    342   EXPECT_EQ(1, ms_dispatcher_->stop_audio_device_counter());
    343   EXPECT_EQ(1, ms_dispatcher_->stop_video_device_counter());
    344 }
    345 
    346 // This test what happens if a video source to a MediaSteam fails to start.
    347 TEST_F(MediaStreamImplTest, MediaVideoSourceFailToStart) {
    348   ms_impl_->RequestUserMedia();
    349   FakeMediaStreamDispatcherRequestUserMediaComplete();
    350   FailToStartMockedVideoSource();
    351   EXPECT_EQ(MediaStreamImplUnderTest::REQUEST_FAILED,
    352             ms_impl_->request_state());
    353   EXPECT_EQ(MEDIA_DEVICE_TRACK_START_FAILURE,
    354             ms_impl_->error_reason());
    355   EXPECT_EQ(1, ms_dispatcher_->request_stream_counter());
    356   EXPECT_EQ(1, ms_dispatcher_->stop_audio_device_counter());
    357   EXPECT_EQ(1, ms_dispatcher_->stop_video_device_counter());
    358 }
    359 
    360 // This test what happens if an audio source fail to initialize.
    361 TEST_F(MediaStreamImplTest, MediaAudioSourceFailToInitialize) {
    362   FailToCreateNextAudioCapturer();
    363   ms_impl_->RequestUserMedia();
    364   FakeMediaStreamDispatcherRequestUserMediaComplete();
    365   StartMockedVideoSource();
    366   EXPECT_EQ(MediaStreamImplUnderTest::REQUEST_FAILED,
    367             ms_impl_->request_state());
    368   EXPECT_EQ(MEDIA_DEVICE_TRACK_START_FAILURE,
    369             ms_impl_->error_reason());
    370   EXPECT_EQ(1, ms_dispatcher_->request_stream_counter());
    371   EXPECT_EQ(1, ms_dispatcher_->stop_audio_device_counter());
    372   EXPECT_EQ(1, ms_dispatcher_->stop_video_device_counter());
    373 }
    374 
    375 // This test what happens if MediaStreamImpl is deleted before a source has
    376 // started.
    377 TEST_F(MediaStreamImplTest, MediaStreamImplShutDown) {
    378   ms_impl_->RequestUserMedia();
    379   FakeMediaStreamDispatcherRequestUserMediaComplete();
    380   EXPECT_EQ(1, ms_dispatcher_->request_stream_counter());
    381   EXPECT_EQ(MediaStreamImplUnderTest::REQUEST_NOT_COMPLETE,
    382             ms_impl_->request_state());
    383   ms_impl_.reset();
    384 }
    385 
    386 // This test what happens if the WebFrame is closed while the MediaStream is
    387 // being generated by the MediaStreamDispatcher.
    388 TEST_F(MediaStreamImplTest, ReloadFrameWhileGeneratingStream) {
    389   ms_impl_->RequestUserMedia();
    390   ms_impl_->FrameWillClose(NULL);
    391   EXPECT_EQ(1, ms_dispatcher_->request_stream_counter());
    392   EXPECT_EQ(0, ms_dispatcher_->stop_audio_device_counter());
    393   EXPECT_EQ(0, ms_dispatcher_->stop_video_device_counter());
    394   EXPECT_EQ(MediaStreamImplUnderTest::REQUEST_NOT_COMPLETE,
    395             ms_impl_->request_state());
    396 }
    397 
    398 // This test what happens if the WebFrame is closed while the sources are being
    399 // started.
    400 TEST_F(MediaStreamImplTest, ReloadFrameWhileGeneratingSources) {
    401   ms_impl_->RequestUserMedia();
    402   FakeMediaStreamDispatcherRequestUserMediaComplete();
    403   EXPECT_EQ(1, ms_dispatcher_->request_stream_counter());
    404   ms_impl_->FrameWillClose(NULL);
    405   EXPECT_EQ(1, ms_dispatcher_->stop_audio_device_counter());
    406   EXPECT_EQ(1, ms_dispatcher_->stop_video_device_counter());
    407   EXPECT_EQ(MediaStreamImplUnderTest::REQUEST_NOT_COMPLETE,
    408             ms_impl_->request_state());
    409 }
    410 
    411 // This test what happens if stop is called on a track after the frame has
    412 // been reloaded.
    413 TEST_F(MediaStreamImplTest, StopTrackAfterReload) {
    414   blink::WebMediaStream mixed_desc = RequestLocalMediaStream();
    415   EXPECT_EQ(1, ms_dispatcher_->request_stream_counter());
    416   ms_impl_->FrameWillClose(NULL);
    417   EXPECT_EQ(1, ms_dispatcher_->stop_audio_device_counter());
    418   EXPECT_EQ(1, ms_dispatcher_->stop_video_device_counter());
    419 
    420   blink::WebVector<blink::WebMediaStreamTrack> audio_tracks;
    421   mixed_desc.audioTracks(audio_tracks);
    422   MediaStreamTrack* audio_track = MediaStreamTrack::GetTrack(audio_tracks[0]);
    423   audio_track->Stop();
    424   EXPECT_EQ(1, ms_dispatcher_->stop_audio_device_counter());
    425 
    426   blink::WebVector<blink::WebMediaStreamTrack> video_tracks;
    427   mixed_desc.videoTracks(video_tracks);
    428   MediaStreamTrack* video_track = MediaStreamTrack::GetTrack(video_tracks[0]);
    429   video_track->Stop();
    430   EXPECT_EQ(1, ms_dispatcher_->stop_video_device_counter());
    431 }
    432 
    433 TEST_F(MediaStreamImplTest, EnumerateMediaDevices) {
    434   ms_impl_->RequestMediaDevices();
    435   FakeMediaStreamDispatcherRequestMediaDevicesComplete();
    436 
    437   EXPECT_EQ(MediaStreamImplUnderTest::REQUEST_SUCCEEDED,
    438             ms_impl_->request_state());
    439 
    440   // Audio input device with matched output ID.
    441   EXPECT_FALSE(ms_impl_->last_devices()[0].deviceId().isEmpty());
    442   EXPECT_EQ(blink::WebMediaDeviceInfo::MediaDeviceKindAudioInput,
    443             ms_impl_->last_devices()[0].kind());
    444   EXPECT_FALSE(ms_impl_->last_devices()[0].label().isEmpty());
    445   EXPECT_FALSE(ms_impl_->last_devices()[0].groupId().isEmpty());
    446 
    447   // Audio input device without matched output ID.
    448   EXPECT_FALSE(ms_impl_->last_devices()[1].deviceId().isEmpty());
    449   EXPECT_EQ(blink::WebMediaDeviceInfo::MediaDeviceKindAudioInput,
    450             ms_impl_->last_devices()[1].kind());
    451   EXPECT_FALSE(ms_impl_->last_devices()[1].label().isEmpty());
    452   EXPECT_FALSE(ms_impl_->last_devices()[1].groupId().isEmpty());
    453 
    454   // Video input device.
    455   EXPECT_FALSE(ms_impl_->last_devices()[2].deviceId().isEmpty());
    456   EXPECT_EQ(blink::WebMediaDeviceInfo::MediaDeviceKindVideoInput,
    457             ms_impl_->last_devices()[2].kind());
    458   EXPECT_FALSE(ms_impl_->last_devices()[2].label().isEmpty());
    459   EXPECT_TRUE(ms_impl_->last_devices()[2].groupId().isEmpty());
    460 
    461   // Audio output device.
    462   EXPECT_FALSE(ms_impl_->last_devices()[3].deviceId().isEmpty());
    463   EXPECT_EQ(blink::WebMediaDeviceInfo::MediaDeviceKindAudioOutput,
    464             ms_impl_->last_devices()[3].kind());
    465   EXPECT_FALSE(ms_impl_->last_devices()[3].label().isEmpty());
    466   EXPECT_FALSE(ms_impl_->last_devices()[3].groupId().isEmpty());
    467 
    468   // Verfify group IDs.
    469   EXPECT_TRUE(ms_impl_->last_devices()[0].groupId().equals(
    470                   ms_impl_->last_devices()[3].groupId()));
    471   EXPECT_FALSE(ms_impl_->last_devices()[1].groupId().equals(
    472                    ms_impl_->last_devices()[3].groupId()));
    473 }
    474 
    475 }  // namespace content
    476