5f9b5c408d7038427ed66eaa85f861bb3b620514
[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 void WebPlaybackSessionModelMediaElement::addClient(WebPlaybackSessionModelClient& client)
170 {
171     ASSERT(!m_clients.contains(&client));
172     m_clients.add(&client);
173 }
174
175 void WebPlaybackSessionModelMediaElement::removeClient(WebPlaybackSessionModelClient& client)
176 {
177     ASSERT(m_clients.contains(&client));
178     m_clients.remove(&client);
179 }
180
181 void WebPlaybackSessionModelMediaElement::play()
182 {
183     if (m_mediaElement)
184         m_mediaElement->play();
185 }
186
187 void WebPlaybackSessionModelMediaElement::pause()
188 {
189     if (m_mediaElement)
190         m_mediaElement->pause();
191 }
192
193 void WebPlaybackSessionModelMediaElement::togglePlayState()
194 {
195     if (m_mediaElement)
196         m_mediaElement->togglePlayState();
197 }
198
199 void WebPlaybackSessionModelMediaElement::beginScrubbing()
200 {
201     if (m_mediaElement)
202         m_mediaElement->beginScrubbing();
203 }
204
205 void WebPlaybackSessionModelMediaElement::endScrubbing()
206 {
207     if (m_mediaElement)
208         m_mediaElement->endScrubbing();
209 }
210
211 void WebPlaybackSessionModelMediaElement::seekToTime(double time)
212 {
213     if (m_mediaElement)
214         m_mediaElement->setCurrentTime(time);
215 }
216
217 void WebPlaybackSessionModelMediaElement::fastSeek(double time)
218 {
219     if (m_mediaElement)
220         m_mediaElement->fastSeek(time);
221 }
222
223 void WebPlaybackSessionModelMediaElement::beginScanningForward()
224 {
225     if (m_mediaElement)
226         m_mediaElement->beginScanning(MediaControllerInterface::Forward);
227 }
228
229 void WebPlaybackSessionModelMediaElement::beginScanningBackward()
230 {
231     if (m_mediaElement)
232         m_mediaElement->beginScanning(MediaControllerInterface::Backward);
233 }
234
235 void WebPlaybackSessionModelMediaElement::endScanning()
236 {
237     if (m_mediaElement)
238         m_mediaElement->endScanning();
239 }
240
241 void WebPlaybackSessionModelMediaElement::selectAudioMediaOption(uint64_t selectedAudioIndex)
242 {
243     if (!m_mediaElement)
244         return;
245
246     for (size_t i = 0, size = m_audioTracksForMenu.size(); i < size; ++i)
247         m_audioTracksForMenu[i]->setEnabled(i == selectedAudioIndex);
248 }
249
250 void WebPlaybackSessionModelMediaElement::selectLegibleMediaOption(uint64_t index)
251 {
252     if (!m_mediaElement)
253         return;
254
255     TextTrack* textTrack;
256     if (index < m_legibleTracksForMenu.size())
257         textTrack = m_legibleTracksForMenu[static_cast<size_t>(index)].get();
258     else
259         textTrack = TextTrack::captionMenuOffItem();
260
261     m_mediaElement->setSelectedTextTrack(textTrack);
262 }
263
264 void WebPlaybackSessionModelMediaElement::togglePictureInPicture()
265 {
266     if (m_mediaElement->fullscreenMode() == MediaPlayerEnums::VideoFullscreenModePictureInPicture)
267         m_mediaElement->exitFullscreen();
268     else
269         m_mediaElement->enterFullscreen(MediaPlayerEnums::VideoFullscreenModePictureInPicture);
270 }
271
272 void WebPlaybackSessionModelMediaElement::updateMediaSelectionOptions()
273 {
274     if (!m_mediaElement)
275         return;
276
277     if (!m_mediaElement->document().page())
278         return;
279
280     auto& captionPreferences = m_mediaElement->document().page()->group().captionPreferences();
281     auto& textTracks = m_mediaElement->textTracks();
282     if (textTracks.length())
283         m_legibleTracksForMenu = captionPreferences.sortedTrackListForMenu(&textTracks);
284     else
285         m_legibleTracksForMenu.clear();
286
287     auto& audioTracks = m_mediaElement->audioTracks();
288     if (audioTracks.length() > 1)
289         m_audioTracksForMenu = captionPreferences.sortedTrackListForMenu(&audioTracks);
290     else
291         m_audioTracksForMenu.clear();
292
293     auto audioOptions = audioMediaSelectionOptions();
294     auto audioIndex = audioMediaSelectedIndex();
295     auto legibleOptions = legibleMediaSelectionOptions();
296     auto legibleIndex = legibleMediaSelectedIndex();
297
298     for (auto client : m_clients) {
299         client->audioMediaSelectionOptionsChanged(audioOptions, audioIndex);
300         client->legibleMediaSelectionOptionsChanged(legibleOptions, legibleIndex);
301     }
302 }
303
304 void WebPlaybackSessionModelMediaElement::updateMediaSelectionIndices()
305 {
306     auto audioIndex = audioMediaSelectedIndex();
307     auto legibleIndex = legibleMediaSelectedIndex();
308
309     for (auto client : m_clients) {
310         client->audioMediaSelectionIndexChanged(audioIndex);
311         client->legibleMediaSelectionIndexChanged(legibleIndex);
312     }
313 }
314
315 double WebPlaybackSessionModelMediaElement::playbackStartedTime() const
316 {
317     if (!m_mediaElement)
318         return 0;
319
320     return m_mediaElement->playbackStartedTime();
321 }
322
323 const Vector<AtomicString>& WebPlaybackSessionModelMediaElement::observedEventNames()
324 {
325     // FIXME(157452): Remove the right-hand constructor notation once NeverDestroyed supports initializer_lists.
326     static NeverDestroyed<Vector<AtomicString>> names = Vector<AtomicString>({
327         eventNames().durationchangeEvent,
328         eventNames().pauseEvent,
329         eventNames().playEvent,
330         eventNames().ratechangeEvent,
331         eventNames().timeupdateEvent,
332         eventNames().addtrackEvent,
333         eventNames().removetrackEvent,
334         eventNames().webkitcurrentplaybacktargetiswirelesschangedEvent,
335     });
336     return names.get();
337 }
338
339 const AtomicString&  WebPlaybackSessionModelMediaElement::eventNameAll()
340 {
341     static NeverDestroyed<AtomicString> eventNameAll("allEvents", AtomicString::ConstructFromLiteral);
342     return eventNameAll;
343 }
344
345 double WebPlaybackSessionModelMediaElement::duration() const
346 {
347     return m_mediaElement ? m_mediaElement->duration() : 0;
348 }
349
350 double WebPlaybackSessionModelMediaElement::currentTime() const
351 {
352     return m_mediaElement ? m_mediaElement->currentTime() : 0;
353 }
354
355 double WebPlaybackSessionModelMediaElement::bufferedTime() const
356 {
357     return m_mediaElement ? m_mediaElement->maxBufferedTime() : 0;
358 }
359
360 bool WebPlaybackSessionModelMediaElement::isPlaying() const
361 {
362     return m_mediaElement ? !m_mediaElement->paused() : false;
363 }
364
365 float WebPlaybackSessionModelMediaElement::playbackRate() const
366 {
367     return m_mediaElement ? m_mediaElement->playbackRate() : 0;
368 }
369
370 Ref<TimeRanges> WebPlaybackSessionModelMediaElement::seekableRanges() const
371 {
372     return m_mediaElement ? m_mediaElement->seekable() : TimeRanges::create();
373 }
374
375 bool WebPlaybackSessionModelMediaElement::canPlayFastReverse() const
376 {
377     return m_mediaElement ? m_mediaElement->minFastReverseRate() < 0.0 : false;
378 }
379
380 Vector<MediaSelectionOption> WebPlaybackSessionModelMediaElement::audioMediaSelectionOptions() const
381 {
382     Vector<MediaSelectionOption> audioOptions;
383
384     if (!m_mediaElement || !m_mediaElement->document().page())
385         return audioOptions;
386
387     auto& captionPreferences = m_mediaElement->document().page()->group().captionPreferences();
388
389     audioOptions.reserveInitialCapacity(m_audioTracksForMenu.size());
390     for (auto& audioTrack : m_audioTracksForMenu)
391         audioOptions.uncheckedAppend(captionPreferences.mediaSelectionOptionForTrack(audioTrack.get()));
392
393     return audioOptions;
394 }
395
396 uint64_t WebPlaybackSessionModelMediaElement::audioMediaSelectedIndex() const
397 {
398     for (size_t index = 0; index < m_audioTracksForMenu.size(); ++index) {
399         if (m_audioTracksForMenu[index]->enabled())
400             return index;
401     }
402     return std::numeric_limits<uint64_t>::max();
403 }
404
405 Vector<MediaSelectionOption> WebPlaybackSessionModelMediaElement::legibleMediaSelectionOptions() const
406 {
407     Vector<MediaSelectionOption> legibleOptions;
408
409     if (!m_mediaElement || !m_mediaElement->document().page())
410         return legibleOptions;
411
412     auto& captionPreferences = m_mediaElement->document().page()->group().captionPreferences();
413
414     for (auto& track : m_legibleTracksForMenu)
415         legibleOptions.append(captionPreferences.mediaSelectionOptionForTrack(track.get()));
416
417     return legibleOptions;
418 }
419
420 uint64_t WebPlaybackSessionModelMediaElement::legibleMediaSelectedIndex() const
421 {
422     uint64_t selectedIndex = std::numeric_limits<uint64_t>::max();
423     uint64_t offIndex = 0;
424     bool trackMenuItemSelected = false;
425
426     auto host = m_mediaElement ? m_mediaElement->mediaControlsHost() : nullptr;
427
428     if (!host)
429         return selectedIndex;
430
431     AtomicString displayMode = host->captionDisplayMode();
432     TextTrack* offItem = host->captionMenuOffItem();
433     TextTrack* automaticItem = host->captionMenuAutomaticItem();
434
435     for (size_t index = 0; index < m_legibleTracksForMenu.size(); index++) {
436         auto& track = m_legibleTracksForMenu[index];
437         if (track == offItem)
438             offIndex = index;
439
440         if (track == automaticItem && displayMode == MediaControlsHost::automaticKeyword()) {
441             selectedIndex = index;
442             trackMenuItemSelected = true;
443         }
444
445         if (displayMode != MediaControlsHost::automaticKeyword() && track->mode() == TextTrack::Mode::Showing) {
446             selectedIndex = index;
447             trackMenuItemSelected = true;
448         }
449     }
450
451     if (offItem && !trackMenuItemSelected && displayMode == MediaControlsHost::forcedOnlyKeyword())
452         selectedIndex = offIndex;
453
454     return selectedIndex;
455 }
456
457 bool WebPlaybackSessionModelMediaElement::externalPlaybackEnabled() const
458 {
459     return m_mediaElement && m_mediaElement->webkitCurrentPlaybackTargetIsWireless();
460 }
461
462 WebPlaybackSessionModel::ExternalPlaybackTargetType WebPlaybackSessionModelMediaElement::externalPlaybackTargetType() const
463 {
464     if (!m_mediaElement || !m_mediaElement->mediaControlsHost())
465         return TargetTypeNone;
466
467     switch (m_mediaElement->mediaControlsHost()->externalDeviceType()) {
468     default:
469         ASSERT_NOT_REACHED();
470         return TargetTypeNone;
471     case MediaControlsHost::DeviceType::None:
472         return TargetTypeNone;
473     case MediaControlsHost::DeviceType::Airplay:
474         return TargetTypeAirPlay;
475     case MediaControlsHost::DeviceType::Tvout:
476         return TargetTypeTVOut;
477     }
478 }
479
480 String WebPlaybackSessionModelMediaElement::externalPlaybackLocalizedDeviceName() const
481 {
482     if (m_mediaElement && m_mediaElement->mediaControlsHost())
483         return m_mediaElement->mediaControlsHost()->externalDeviceDisplayName();
484     return emptyString();
485 }
486
487 bool WebPlaybackSessionModelMediaElement::wirelessVideoPlaybackDisabled() const
488 {
489     return m_mediaElement && m_mediaElement->mediaSession().wirelessVideoPlaybackDisabled(*m_mediaElement);
490 }
491
492 }
493
494 #endif