Source/WebCore:
[WebKit-https.git] / Source / WebCore / platform / cocoa / WebPlaybackSessionModelMediaElement.mm
1 /*
2  * Copyright (C) 2016-2017 Apple Inc. All rights reserved.
3  *
4  * Redistribution and use in source and binary forms, with or without
5  * modification, are permitted provided that the following conditions
6  * are met:
7  * 1. Redistributions of source code must retain the above copyright
8  *    notice, this list of conditions and the following disclaimer.
9  * 2. Redistributions in binary form must reproduce the above copyright
10  *    notice, this list of conditions and the following disclaimer in the
11  *    documentation and/or other materials provided with the distribution.
12  *
13  * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS''
14  * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
15  * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
16  * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS
17  * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
18  * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
19  * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
20  * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
21  * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
22  * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
23  * THE POSSIBILITY OF SUCH DAMAGE.
24  */
25
26 #import "config.h"
27 #import "WebPlaybackSessionModelMediaElement.h"
28
29 #if PLATFORM(IOS) || (PLATFORM(MAC) && ENABLE(VIDEO_PRESENTATION_MODE))
30
31 #import "AudioTrackList.h"
32 #import "Event.h"
33 #import "EventListener.h"
34 #import "EventNames.h"
35 #import "HTMLElement.h"
36 #import "HTMLMediaElement.h"
37 #import "Logging.h"
38 #import "MediaControlsHost.h"
39 #import "MediaSelectionOption.h"
40 #import "Page.h"
41 #import "PageGroup.h"
42 #import "SoftLinking.h"
43 #import "TextTrackList.h"
44 #import "TimeRanges.h"
45 #import "WebVideoFullscreenInterface.h"
46 #import <QuartzCore/CoreAnimation.h>
47 #import <wtf/NeverDestroyed.h>
48
49 namespace WebCore {
50
51 WebPlaybackSessionModelMediaElement::WebPlaybackSessionModelMediaElement()
52     : EventListener(EventListener::CPPEventListenerType)
53 {
54 }
55
56 WebPlaybackSessionModelMediaElement::~WebPlaybackSessionModelMediaElement()
57 {
58 }
59
60 void WebPlaybackSessionModelMediaElement::setMediaElement(HTMLMediaElement* mediaElement)
61 {
62     if (m_mediaElement == mediaElement)
63         return;
64
65     if (m_mediaElement && m_isListening) {
66         for (auto& eventName : observedEventNames())
67             m_mediaElement->removeEventListener(eventName, *this, false);
68         m_mediaElement->audioTracks().removeEventListener(eventNames().changeEvent, *this, false);
69         m_mediaElement->textTracks().removeEventListener(eventNames().changeEvent, *this, false);
70     }
71     m_isListening = false;
72
73     if (m_mediaElement)
74         m_mediaElement->resetPlaybackSessionState();
75
76     m_mediaElement = mediaElement;
77
78     if (m_mediaElement) {
79         for (auto& eventName : observedEventNames())
80             m_mediaElement->addEventListener(eventName, *this, false);
81         m_mediaElement->audioTracks().addEventListener(eventNames().changeEvent, *this, false);
82         m_mediaElement->textTracks().addEventListener(eventNames().changeEvent, *this, false);
83         m_isListening = true;
84     }
85
86     updateForEventName(eventNameAll());
87 }
88
89 void WebPlaybackSessionModelMediaElement::handleEvent(WebCore::ScriptExecutionContext*, WebCore::Event* event)
90 {
91     updateForEventName(event->type());
92 }
93
94 void WebPlaybackSessionModelMediaElement::updateForEventName(const WTF::AtomicString& eventName)
95 {
96     if (m_clients.isEmpty())
97         return;
98
99     bool all = eventName == eventNameAll();
100
101     if (all
102         || eventName == eventNames().durationchangeEvent) {
103         double duration = this->duration();
104         for (auto client : m_clients)
105             client->durationChanged(duration);
106         // These is no standard event for minFastReverseRateChange; duration change is a reasonable proxy for it.
107         // It happens every time a new item becomes ready to play.
108         bool canPlayFastReverse = this->canPlayFastReverse();
109         for (auto client : m_clients)
110             client->canPlayFastReverseChanged(canPlayFastReverse);
111     }
112
113     if (all
114         || eventName == eventNames().playEvent
115         || eventName == eventNames().playingEvent) {
116         for (auto client : m_clients)
117             client->playbackStartedTimeChanged(playbackStartedTime());
118     }
119
120     if (all
121         || eventName == eventNames().pauseEvent
122         || eventName == eventNames().playEvent
123         || eventName == eventNames().ratechangeEvent) {
124         bool isPlaying = this->isPlaying();
125         float playbackRate = this->playbackRate();
126         for (auto client : m_clients)
127             client->rateChanged(isPlaying, playbackRate);
128     }
129
130     if (all
131         || eventName == eventNames().timeupdateEvent) {
132         auto currentTime = this->currentTime();
133         auto anchorTime = [[NSProcessInfo processInfo] systemUptime];
134         auto bufferedTime = this->bufferedTime();
135         auto seekableRanges = this->seekableRanges();
136
137         for (auto client : m_clients) {
138             client->currentTimeChanged(currentTime, anchorTime);
139             client->bufferedTimeChanged(bufferedTime);
140             // FIXME: 130788 - find a better event from which to update seekable ranges.
141             client->seekableRangesChanged(seekableRanges);
142         }
143     }
144
145     if (all
146         || eventName == eventNames().addtrackEvent
147         || eventName == eventNames().removetrackEvent)
148         updateMediaSelectionOptions();
149
150     if (all
151         || eventName == eventNames().webkitcurrentplaybacktargetiswirelesschangedEvent) {
152         bool enabled = externalPlaybackEnabled();
153         ExternalPlaybackTargetType targetType = externalPlaybackTargetType();
154         String localizedDeviceName = externalPlaybackLocalizedDeviceName();
155
156         bool wirelessVideoPlaybackDisabled = this->wirelessVideoPlaybackDisabled();
157
158         for (auto client : m_clients) {
159             client->externalPlaybackChanged(enabled, targetType, localizedDeviceName);
160             client->wirelessVideoPlaybackDisabledChanged(wirelessVideoPlaybackDisabled);
161         }
162     }
163
164     // We don't call updateMediaSelectionIndices() in the all case, since
165     // updateMediaSelectionOptions() will also update the selection indices.
166     if (eventName == eventNames().changeEvent)
167         updateMediaSelectionIndices();
168
169     if (all
170         || eventName == eventNames().volumechangeEvent) {
171         for (auto client : m_clients)
172             client->mutedChanged(isMuted());
173     }
174 }
175 void WebPlaybackSessionModelMediaElement::addClient(WebPlaybackSessionModelClient& client)
176 {
177     ASSERT(!m_clients.contains(&client));
178     m_clients.add(&client);
179 }
180
181 void WebPlaybackSessionModelMediaElement::removeClient(WebPlaybackSessionModelClient& client)
182 {
183     ASSERT(m_clients.contains(&client));
184     m_clients.remove(&client);
185 }
186
187 void WebPlaybackSessionModelMediaElement::play()
188 {
189     if (m_mediaElement)
190         m_mediaElement->play();
191 }
192
193 void WebPlaybackSessionModelMediaElement::pause()
194 {
195     if (m_mediaElement)
196         m_mediaElement->pause();
197 }
198
199 void WebPlaybackSessionModelMediaElement::togglePlayState()
200 {
201     if (m_mediaElement)
202         m_mediaElement->togglePlayState();
203 }
204
205 void WebPlaybackSessionModelMediaElement::beginScrubbing()
206 {
207     if (m_mediaElement)
208         m_mediaElement->beginScrubbing();
209 }
210
211 void WebPlaybackSessionModelMediaElement::endScrubbing()
212 {
213     if (m_mediaElement)
214         m_mediaElement->endScrubbing();
215 }
216
217 void WebPlaybackSessionModelMediaElement::seekToTime(double time)
218 {
219     if (m_mediaElement)
220         m_mediaElement->setCurrentTime(time);
221 }
222
223 void WebPlaybackSessionModelMediaElement::fastSeek(double time)
224 {
225     if (m_mediaElement)
226         m_mediaElement->fastSeek(time);
227 }
228
229 void WebPlaybackSessionModelMediaElement::beginScanningForward()
230 {
231     if (m_mediaElement)
232         m_mediaElement->beginScanning(MediaControllerInterface::Forward);
233 }
234
235 void WebPlaybackSessionModelMediaElement::beginScanningBackward()
236 {
237     if (m_mediaElement)
238         m_mediaElement->beginScanning(MediaControllerInterface::Backward);
239 }
240
241 void WebPlaybackSessionModelMediaElement::endScanning()
242 {
243     if (m_mediaElement)
244         m_mediaElement->endScanning();
245 }
246
247 void WebPlaybackSessionModelMediaElement::selectAudioMediaOption(uint64_t selectedAudioIndex)
248 {
249     if (!m_mediaElement)
250         return;
251
252     for (size_t i = 0, size = m_audioTracksForMenu.size(); i < size; ++i)
253         m_audioTracksForMenu[i]->setEnabled(i == selectedAudioIndex);
254 }
255
256 void WebPlaybackSessionModelMediaElement::selectLegibleMediaOption(uint64_t index)
257 {
258     if (!m_mediaElement)
259         return;
260
261     TextTrack* textTrack;
262     if (index < m_legibleTracksForMenu.size())
263         textTrack = m_legibleTracksForMenu[static_cast<size_t>(index)].get();
264     else
265         textTrack = TextTrack::captionMenuOffItem();
266
267     m_mediaElement->setSelectedTextTrack(textTrack);
268 }
269
270 void WebPlaybackSessionModelMediaElement::togglePictureInPicture()
271 {
272     if (m_mediaElement->fullscreenMode() == MediaPlayerEnums::VideoFullscreenModePictureInPicture)
273         m_mediaElement->exitFullscreen();
274     else
275         m_mediaElement->enterFullscreen(MediaPlayerEnums::VideoFullscreenModePictureInPicture);
276 }
277
278 void WebPlaybackSessionModelMediaElement::toggleMuted()
279 {
280     if (m_mediaElement)
281         m_mediaElement->setMuted(!m_mediaElement->muted());
282 }
283
284 void WebPlaybackSessionModelMediaElement::updateMediaSelectionOptions()
285 {
286     if (!m_mediaElement)
287         return;
288
289     if (!m_mediaElement->document().page())
290         return;
291
292     auto& captionPreferences = m_mediaElement->document().page()->group().captionPreferences();
293     auto& textTracks = m_mediaElement->textTracks();
294     if (textTracks.length())
295         m_legibleTracksForMenu = captionPreferences.sortedTrackListForMenu(&textTracks);
296     else
297         m_legibleTracksForMenu.clear();
298
299     auto& audioTracks = m_mediaElement->audioTracks();
300     if (audioTracks.length() > 1)
301         m_audioTracksForMenu = captionPreferences.sortedTrackListForMenu(&audioTracks);
302     else
303         m_audioTracksForMenu.clear();
304
305     auto audioOptions = audioMediaSelectionOptions();
306     auto audioIndex = audioMediaSelectedIndex();
307     auto legibleOptions = legibleMediaSelectionOptions();
308     auto legibleIndex = legibleMediaSelectedIndex();
309
310     for (auto client : m_clients) {
311         client->audioMediaSelectionOptionsChanged(audioOptions, audioIndex);
312         client->legibleMediaSelectionOptionsChanged(legibleOptions, legibleIndex);
313     }
314 }
315
316 void WebPlaybackSessionModelMediaElement::updateMediaSelectionIndices()
317 {
318     auto audioIndex = audioMediaSelectedIndex();
319     auto legibleIndex = legibleMediaSelectedIndex();
320
321     for (auto client : m_clients) {
322         client->audioMediaSelectionIndexChanged(audioIndex);
323         client->legibleMediaSelectionIndexChanged(legibleIndex);
324     }
325 }
326
327 double WebPlaybackSessionModelMediaElement::playbackStartedTime() const
328 {
329     if (!m_mediaElement)
330         return 0;
331
332     return m_mediaElement->playbackStartedTime();
333 }
334
335 const Vector<AtomicString>& WebPlaybackSessionModelMediaElement::observedEventNames()
336 {
337     // FIXME(157452): Remove the right-hand constructor notation once NeverDestroyed supports initializer_lists.
338     static NeverDestroyed<Vector<AtomicString>> names = Vector<AtomicString>({
339         eventNames().durationchangeEvent,
340         eventNames().pauseEvent,
341         eventNames().playEvent,
342         eventNames().ratechangeEvent,
343         eventNames().timeupdateEvent,
344         eventNames().addtrackEvent,
345         eventNames().removetrackEvent,
346         eventNames().volumechangeEvent,
347         eventNames().webkitcurrentplaybacktargetiswirelesschangedEvent,
348     });
349     return names.get();
350 }
351
352 const AtomicString&  WebPlaybackSessionModelMediaElement::eventNameAll()
353 {
354     static NeverDestroyed<AtomicString> eventNameAll("allEvents", AtomicString::ConstructFromLiteral);
355     return eventNameAll;
356 }
357
358 double WebPlaybackSessionModelMediaElement::duration() const
359 {
360     return m_mediaElement ? m_mediaElement->duration() : 0;
361 }
362
363 double WebPlaybackSessionModelMediaElement::currentTime() const
364 {
365     return m_mediaElement ? m_mediaElement->currentTime() : 0;
366 }
367
368 double WebPlaybackSessionModelMediaElement::bufferedTime() const
369 {
370     return m_mediaElement ? m_mediaElement->maxBufferedTime() : 0;
371 }
372
373 bool WebPlaybackSessionModelMediaElement::isPlaying() const
374 {
375     return m_mediaElement ? !m_mediaElement->paused() : false;
376 }
377
378 float WebPlaybackSessionModelMediaElement::playbackRate() const
379 {
380     return m_mediaElement ? m_mediaElement->playbackRate() : 0;
381 }
382
383 Ref<TimeRanges> WebPlaybackSessionModelMediaElement::seekableRanges() const
384 {
385     return m_mediaElement ? m_mediaElement->seekable() : TimeRanges::create();
386 }
387
388 bool WebPlaybackSessionModelMediaElement::canPlayFastReverse() const
389 {
390     return m_mediaElement ? m_mediaElement->minFastReverseRate() < 0.0 : false;
391 }
392
393 Vector<MediaSelectionOption> WebPlaybackSessionModelMediaElement::audioMediaSelectionOptions() const
394 {
395     Vector<MediaSelectionOption> audioOptions;
396
397     if (!m_mediaElement || !m_mediaElement->document().page())
398         return audioOptions;
399
400     auto& captionPreferences = m_mediaElement->document().page()->group().captionPreferences();
401
402     audioOptions.reserveInitialCapacity(m_audioTracksForMenu.size());
403     for (auto& audioTrack : m_audioTracksForMenu)
404         audioOptions.uncheckedAppend(captionPreferences.mediaSelectionOptionForTrack(audioTrack.get()));
405
406     return audioOptions;
407 }
408
409 uint64_t WebPlaybackSessionModelMediaElement::audioMediaSelectedIndex() const
410 {
411     for (size_t index = 0; index < m_audioTracksForMenu.size(); ++index) {
412         if (m_audioTracksForMenu[index]->enabled())
413             return index;
414     }
415     return std::numeric_limits<uint64_t>::max();
416 }
417
418 Vector<MediaSelectionOption> WebPlaybackSessionModelMediaElement::legibleMediaSelectionOptions() const
419 {
420     Vector<MediaSelectionOption> legibleOptions;
421
422     if (!m_mediaElement || !m_mediaElement->document().page())
423         return legibleOptions;
424
425     auto& captionPreferences = m_mediaElement->document().page()->group().captionPreferences();
426
427     for (auto& track : m_legibleTracksForMenu)
428         legibleOptions.append(captionPreferences.mediaSelectionOptionForTrack(track.get()));
429
430     return legibleOptions;
431 }
432
433 uint64_t WebPlaybackSessionModelMediaElement::legibleMediaSelectedIndex() const
434 {
435     uint64_t selectedIndex = std::numeric_limits<uint64_t>::max();
436     uint64_t offIndex = 0;
437     bool trackMenuItemSelected = false;
438
439     auto host = m_mediaElement ? m_mediaElement->mediaControlsHost() : nullptr;
440
441     if (!host)
442         return selectedIndex;
443
444     AtomicString displayMode = host->captionDisplayMode();
445     TextTrack* offItem = host->captionMenuOffItem();
446     TextTrack* automaticItem = host->captionMenuAutomaticItem();
447
448     for (size_t index = 0; index < m_legibleTracksForMenu.size(); index++) {
449         auto& track = m_legibleTracksForMenu[index];
450         if (track == offItem)
451             offIndex = index;
452
453         if (track == automaticItem && displayMode == MediaControlsHost::automaticKeyword()) {
454             selectedIndex = index;
455             trackMenuItemSelected = true;
456         }
457
458         if (displayMode != MediaControlsHost::automaticKeyword() && track->mode() == TextTrack::Mode::Showing) {
459             selectedIndex = index;
460             trackMenuItemSelected = true;
461         }
462     }
463
464     if (offItem && !trackMenuItemSelected && displayMode == MediaControlsHost::forcedOnlyKeyword())
465         selectedIndex = offIndex;
466
467     return selectedIndex;
468 }
469
470 bool WebPlaybackSessionModelMediaElement::externalPlaybackEnabled() const
471 {
472     return m_mediaElement && m_mediaElement->webkitCurrentPlaybackTargetIsWireless();
473 }
474
475 WebPlaybackSessionModel::ExternalPlaybackTargetType WebPlaybackSessionModelMediaElement::externalPlaybackTargetType() const
476 {
477     if (!m_mediaElement || !m_mediaElement->mediaControlsHost())
478         return TargetTypeNone;
479
480     switch (m_mediaElement->mediaControlsHost()->externalDeviceType()) {
481     default:
482         ASSERT_NOT_REACHED();
483         return TargetTypeNone;
484     case MediaControlsHost::DeviceType::None:
485         return TargetTypeNone;
486     case MediaControlsHost::DeviceType::Airplay:
487         return TargetTypeAirPlay;
488     case MediaControlsHost::DeviceType::Tvout:
489         return TargetTypeTVOut;
490     }
491 }
492
493 String WebPlaybackSessionModelMediaElement::externalPlaybackLocalizedDeviceName() const
494 {
495     if (m_mediaElement && m_mediaElement->mediaControlsHost())
496         return m_mediaElement->mediaControlsHost()->externalDeviceDisplayName();
497     return emptyString();
498 }
499
500 bool WebPlaybackSessionModelMediaElement::wirelessVideoPlaybackDisabled() const
501 {
502     return m_mediaElement && m_mediaElement->mediaSession().wirelessVideoPlaybackDisabled(*m_mediaElement);
503 }
504
505 bool WebPlaybackSessionModelMediaElement::isMuted() const
506 {
507     return m_mediaElement ? m_mediaElement->muted() : false;
508 }
509
510 }
511
512 #endif