blob: 603d1a860562ef30062c50d73e4febaa63dd51c6 [file] [log] [blame]
/*
* Copyright (C) 2014-2019 Apple Inc. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
* OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#import "config.h"
#import "PlaybackSessionInterfaceAVKit.h"
#if PLATFORM(COCOA) && HAVE(AVKIT)
#import "Logging.h"
#import "MediaSelectionOption.h"
#import "PlaybackSessionModel.h"
#import "TimeRanges.h"
#import "WebAVPlayerController.h"
#import <AVFoundation/AVTime.h>
#import <pal/spi/cocoa/AVKitSPI.h>
#import <wtf/RetainPtr.h>
#import <wtf/cocoa/VectorCocoa.h>
#import <wtf/text/CString.h>
#import <wtf/text/WTFString.h>
#import <pal/cf/CoreMediaSoftLink.h>
#import <pal/cocoa/AVFoundationSoftLink.h>
SOFTLINK_AVKIT_FRAMEWORK()
SOFT_LINK_CLASS_OPTIONAL(AVKit, AVValueTiming)
namespace WebCore {
PlaybackSessionInterfaceAVKit::PlaybackSessionInterfaceAVKit(PlaybackSessionModel& model)
: m_playerController(adoptNS([[WebAVPlayerController alloc] init]))
, m_playbackSessionModel(&model)
{
ASSERT(isUIThread());
model.addClient(*this);
[m_playerController setPlaybackSessionInterface:this];
[m_playerController setDelegate:&model];
durationChanged(model.duration());
currentTimeChanged(model.currentTime(), [[NSProcessInfo processInfo] systemUptime]);
bufferedTimeChanged(model.bufferedTime());
OptionSet<PlaybackSessionModel::PlaybackState> playbackState;
if (model.isPlaying())
playbackState.add(PlaybackSessionModel::PlaybackState::Playing);
if (model.isStalled())
playbackState.add(PlaybackSessionModel::PlaybackState::Stalled);
rateChanged(playbackState, model.playbackRate(), model.defaultPlaybackRate());
seekableRangesChanged(model.seekableRanges(), model.seekableTimeRangesLastModifiedTime(), model.liveUpdateInterval());
canPlayFastReverseChanged(model.canPlayFastReverse());
audioMediaSelectionOptionsChanged(model.audioMediaSelectionOptions(), model.audioMediaSelectedIndex());
legibleMediaSelectionOptionsChanged(model.legibleMediaSelectionOptions(), model.legibleMediaSelectedIndex());
externalPlaybackChanged(model.externalPlaybackEnabled(), model.externalPlaybackTargetType(), model.externalPlaybackLocalizedDeviceName());
wirelessVideoPlaybackDisabledChanged(model.wirelessVideoPlaybackDisabled());
}
PlaybackSessionInterfaceAVKit::~PlaybackSessionInterfaceAVKit()
{
ASSERT(isUIThread());
[m_playerController setPlaybackSessionInterface:nullptr];
[m_playerController setExternalPlaybackActive:false];
invalidate();
}
PlaybackSessionModel* PlaybackSessionInterfaceAVKit::playbackSessionModel() const
{
return m_playbackSessionModel;
}
void PlaybackSessionInterfaceAVKit::durationChanged(double duration)
{
WebAVPlayerController* playerController = m_playerController.get();
playerController.contentDuration = duration;
playerController.contentDurationWithinEndTimes = duration;
// FIXME: we take this as an indication that playback is ready.
playerController.canPlay = YES;
playerController.canPause = YES;
playerController.canTogglePlayback = YES;
playerController.hasEnabledAudio = YES;
playerController.canSeek = YES;
playerController.status = AVPlayerControllerStatusReadyToPlay;
}
void PlaybackSessionInterfaceAVKit::currentTimeChanged(double currentTime, double anchorTime)
{
if ([m_playerController isScrubbing])
return;
NSTimeInterval anchorTimeStamp = ![m_playerController rate] ? NAN : anchorTime;
AVValueTiming *timing = [getAVValueTimingClass() valueTimingWithAnchorValue:currentTime
anchorTimeStamp:anchorTimeStamp rate:0];
[m_playerController setTiming:timing];
}
void PlaybackSessionInterfaceAVKit::bufferedTimeChanged(double bufferedTime)
{
WebAVPlayerController* playerController = m_playerController.get();
double duration = playerController.contentDuration;
double normalizedBufferedTime;
if (!duration)
normalizedBufferedTime = 0;
else
normalizedBufferedTime = bufferedTime / duration;
playerController.loadedTimeRanges = @[@0, @(normalizedBufferedTime)];
}
void PlaybackSessionInterfaceAVKit::rateChanged(OptionSet<PlaybackSessionModel::PlaybackState> playbackState, double playbackRate, double defaultPlaybackRate)
{
[m_playerController setDefaultPlaybackRate:defaultPlaybackRate fromJavaScript:YES];
if (!playbackState.contains(PlaybackSessionModel::PlaybackState::Stalled))
[m_playerController setRate:playbackState.contains(PlaybackSessionModel::PlaybackState::Playing) ? playbackRate : 0. fromJavaScript:YES];
}
void PlaybackSessionInterfaceAVKit::seekableRangesChanged(const TimeRanges& timeRanges, double lastModifiedTime, double liveUpdateInterval)
{
RetainPtr<NSMutableArray> seekableRanges = adoptNS([[NSMutableArray alloc] init]);
#if !PLATFORM(WATCHOS)
for (unsigned i = 0; i < timeRanges.length(); i++) {
double start = timeRanges.start(i).releaseReturnValue();
double end = timeRanges.end(i).releaseReturnValue();
CMTimeRange range = PAL::CMTimeRangeMake(PAL::CMTimeMakeWithSeconds(start, 1000), PAL::CMTimeMakeWithSeconds(end-start, 1000));
[seekableRanges addObject:[NSValue valueWithCMTimeRange:range]];
}
#else
UNUSED_PARAM(timeRanges);
#endif
[m_playerController setSeekableTimeRanges:seekableRanges.get()];
[m_playerController setSeekableTimeRangesLastModifiedTime: lastModifiedTime];
[m_playerController setLiveUpdateInterval:liveUpdateInterval];
}
void PlaybackSessionInterfaceAVKit::canPlayFastReverseChanged(bool canPlayFastReverse)
{
[m_playerController setCanScanBackward:canPlayFastReverse];
}
static AVMediaType toAVMediaType(MediaSelectionOption::MediaType type)
{
switch (type) {
case MediaSelectionOption::MediaType::Audio:
return AVMediaTypeAudio;
break;
case MediaSelectionOption::MediaType::Subtitles:
return AVMediaTypeSubtitle;
break;
case MediaSelectionOption::MediaType::Captions:
return AVMediaTypeClosedCaption;
break;
case MediaSelectionOption::MediaType::Metadata:
return AVMediaTypeMetadata;
break;
case MediaSelectionOption::MediaType::Unknown:
ASSERT_NOT_REACHED();
break;
}
return AVMediaTypeMetadata;
}
static RetainPtr<NSArray> mediaSelectionOptions(const Vector<MediaSelectionOption>& options)
{
return createNSArray(options, [] (auto& option) {
return adoptNS([[WebAVMediaSelectionOption alloc] initWithMediaType:toAVMediaType(option.mediaType) displayName:option.displayName]);
});
}
void PlaybackSessionInterfaceAVKit::audioMediaSelectionOptionsChanged(const Vector<MediaSelectionOption>& options, uint64_t selectedIndex)
{
auto webOptions = mediaSelectionOptions(options);
[m_playerController setAudioMediaSelectionOptions:webOptions.get()];
if (selectedIndex < [webOptions count])
[m_playerController setCurrentAudioMediaSelectionOption:[webOptions objectAtIndex:static_cast<NSUInteger>(selectedIndex)]];
}
void PlaybackSessionInterfaceAVKit::legibleMediaSelectionOptionsChanged(const Vector<MediaSelectionOption>& options, uint64_t selectedIndex)
{
auto webOptions = mediaSelectionOptions(options);
[m_playerController setLegibleMediaSelectionOptions:webOptions.get()];
if (selectedIndex < [webOptions count])
[m_playerController setCurrentLegibleMediaSelectionOption:[webOptions objectAtIndex:static_cast<NSUInteger>(selectedIndex)]];
}
void PlaybackSessionInterfaceAVKit::externalPlaybackChanged(bool enabled, PlaybackSessionModel::ExternalPlaybackTargetType targetType, const String& localizedDeviceName)
{
AVPlayerControllerExternalPlaybackType externalPlaybackType = AVPlayerControllerExternalPlaybackTypeNone;
if (enabled && targetType == PlaybackSessionModel::TargetTypeAirPlay)
externalPlaybackType = AVPlayerControllerExternalPlaybackTypeAirPlay;
else if (enabled && targetType == PlaybackSessionModel::TargetTypeTVOut)
externalPlaybackType = AVPlayerControllerExternalPlaybackTypeTVOut;
WebAVPlayerController* playerController = m_playerController.get();
playerController.externalPlaybackAirPlayDeviceLocalizedName = localizedDeviceName;
playerController.externalPlaybackType = externalPlaybackType;
playerController.externalPlaybackActive = enabled;
}
void PlaybackSessionInterfaceAVKit::wirelessVideoPlaybackDisabledChanged(bool disabled)
{
[m_playerController setAllowsExternalPlayback:!disabled];
}
void PlaybackSessionInterfaceAVKit::mutedChanged(bool muted)
{
[m_playerController setMuted:muted];
}
void PlaybackSessionInterfaceAVKit::volumeChanged(double volume)
{
[m_playerController volumeChanged:volume];
}
void PlaybackSessionInterfaceAVKit::invalidate()
{
if (!m_playbackSessionModel)
return;
[m_playerController setDelegate:nullptr];
m_playbackSessionModel->removeClient(*this);
m_playbackSessionModel = nullptr;
}
void PlaybackSessionInterfaceAVKit::modelDestroyed()
{
ASSERT(isUIThread());
invalidate();
ASSERT(!m_playbackSessionModel);
}
}
#endif // PLATFORM(COCOA) && HAVE(AVKIT)