/* * Copyright (C) 2013 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 "AVTrackPrivateAVFObjCImpl.h" #if ENABLE(VIDEO) #import "FormatDescriptionUtilities.h" #import "MediaSelectionGroupAVFObjC.h" #import "PlatformAudioTrackConfiguration.h" #import "PlatformVideoTrackConfiguration.h" #import "SharedBuffer.h" #import #import #import #import #import #import #import #import #import @class AVMediaSelectionOption; @interface AVMediaSelectionOption (WebKitInternal) - (id)optionID; @end namespace WebCore { static NSArray* assetTrackConfigurationKeyNames() { static NSArray* keys = [[NSArray alloc] initWithObjects:@"formatDescriptions", @"estimatedDataRate", @"nominalFrameRate", nil]; return keys; } AVTrackPrivateAVFObjCImpl::AVTrackPrivateAVFObjCImpl(AVPlayerItemTrack* track) : m_playerItemTrack(track) , m_assetTrack([track assetTrack]) { initializeAssetTrack(); } AVTrackPrivateAVFObjCImpl::AVTrackPrivateAVFObjCImpl(AVAssetTrack* track) : m_assetTrack(track) { initializeAssetTrack(); } AVTrackPrivateAVFObjCImpl::AVTrackPrivateAVFObjCImpl(MediaSelectionOptionAVFObjC& option) : m_mediaSelectionOption(&option) , m_assetTrack(option.assetTrack()) { initializeAssetTrack(); } AVTrackPrivateAVFObjCImpl::~AVTrackPrivateAVFObjCImpl() { } void AVTrackPrivateAVFObjCImpl::initializeAssetTrack() { if (!m_assetTrack) return; [m_assetTrack loadValuesAsynchronouslyForKeys:assetTrackConfigurationKeyNames() completionHandler:[weakThis = WeakPtr(this)] () mutable { callOnMainThread([weakThis = WTFMove(weakThis)] { if (weakThis && weakThis->m_audioTrackConfigurationObserver) (*weakThis->m_audioTrackConfigurationObserver)(); if (weakThis && weakThis->m_videoTrackConfigurationObserver) (*weakThis->m_videoTrackConfigurationObserver)(); }); }]; } bool AVTrackPrivateAVFObjCImpl::enabled() const { if (m_playerItemTrack) return [m_playerItemTrack isEnabled]; if (m_mediaSelectionOption) return m_mediaSelectionOption->selected(); ASSERT_NOT_REACHED(); return false; } void AVTrackPrivateAVFObjCImpl::setEnabled(bool enabled) { if (m_playerItemTrack) [m_playerItemTrack setEnabled:enabled]; else if (m_mediaSelectionOption) m_mediaSelectionOption->setSelected(enabled); else ASSERT_NOT_REACHED(); } AudioTrackPrivate::Kind AVTrackPrivateAVFObjCImpl::audioKind() const { if (m_assetTrack) { if ([m_assetTrack hasMediaCharacteristic:AVMediaCharacteristicIsAuxiliaryContent]) return AudioTrackPrivate::Alternative; if ([m_assetTrack hasMediaCharacteristic:AVMediaCharacteristicDescribesVideoForAccessibility]) return AudioTrackPrivate::Description; if ([m_assetTrack hasMediaCharacteristic:AVMediaCharacteristicIsMainProgramContent]) return AudioTrackPrivate::Main; return AudioTrackPrivate::None; } if (m_mediaSelectionOption) { AVMediaSelectionOption *option = m_mediaSelectionOption->avMediaSelectionOption(); if ([option hasMediaCharacteristic:AVMediaCharacteristicIsAuxiliaryContent]) return AudioTrackPrivate::Alternative; if ([option hasMediaCharacteristic:AVMediaCharacteristicDescribesVideoForAccessibility]) return AudioTrackPrivate::Description; if ([option hasMediaCharacteristic:AVMediaCharacteristicIsMainProgramContent]) return AudioTrackPrivate::Main; return AudioTrackPrivate::None; } ASSERT_NOT_REACHED(); return AudioTrackPrivate::None; } VideoTrackPrivate::Kind AVTrackPrivateAVFObjCImpl::videoKind() const { if (m_assetTrack) { if ([m_assetTrack hasMediaCharacteristic:AVMediaCharacteristicDescribesVideoForAccessibility]) return VideoTrackPrivate::Sign; if ([m_assetTrack hasMediaCharacteristic:AVMediaCharacteristicTranscribesSpokenDialogForAccessibility]) return VideoTrackPrivate::Captions; if ([m_assetTrack hasMediaCharacteristic:AVMediaCharacteristicIsAuxiliaryContent]) return VideoTrackPrivate::Alternative; if ([m_assetTrack hasMediaCharacteristic:AVMediaCharacteristicIsMainProgramContent]) return VideoTrackPrivate::Main; return VideoTrackPrivate::None; } if (m_mediaSelectionOption) { AVMediaSelectionOption *option = m_mediaSelectionOption->avMediaSelectionOption(); if ([option hasMediaCharacteristic:AVMediaCharacteristicDescribesVideoForAccessibility]) return VideoTrackPrivate::Sign; if ([option hasMediaCharacteristic:AVMediaCharacteristicTranscribesSpokenDialogForAccessibility]) return VideoTrackPrivate::Captions; if ([option hasMediaCharacteristic:AVMediaCharacteristicIsAuxiliaryContent]) return VideoTrackPrivate::Alternative; if ([option hasMediaCharacteristic:AVMediaCharacteristicIsMainProgramContent]) return VideoTrackPrivate::Main; return VideoTrackPrivate::None; } ASSERT_NOT_REACHED(); return VideoTrackPrivate::None; } int AVTrackPrivateAVFObjCImpl::index() const { if (m_assetTrack) return [[[m_assetTrack asset] tracks] indexOfObject:m_assetTrack.get()]; if (m_mediaSelectionOption) return [[[m_playerItem asset] tracks] count] + m_mediaSelectionOption->index(); ASSERT_NOT_REACHED(); return 0; } AtomString AVTrackPrivateAVFObjCImpl::id() const { if (m_assetTrack) return AtomString::number([m_assetTrack trackID]); if (m_mediaSelectionOption) return [[m_mediaSelectionOption->avMediaSelectionOption() optionID] stringValue]; ASSERT_NOT_REACHED(); return emptyAtom(); } AtomString AVTrackPrivateAVFObjCImpl::label() const { NSArray *commonMetadata = nil; if (m_assetTrack) commonMetadata = [m_assetTrack commonMetadata]; else if (m_mediaSelectionOption) commonMetadata = [m_mediaSelectionOption->avMediaSelectionOption() commonMetadata]; else ASSERT_NOT_REACHED(); NSArray *titles = [PAL::getAVMetadataItemClass() metadataItemsFromArray:commonMetadata withKey:AVMetadataCommonKeyTitle keySpace:AVMetadataKeySpaceCommon]; if (![titles count]) return emptyAtom(); // If possible, return a title in one of the user's preferred languages. NSArray *titlesForPreferredLanguages = [PAL::getAVMetadataItemClass() metadataItemsFromArray:titles filteredAndSortedAccordingToPreferredLanguages:[NSLocale preferredLanguages]]; if ([titlesForPreferredLanguages count]) return [[titlesForPreferredLanguages objectAtIndex:0] stringValue]; return [[titles objectAtIndex:0] stringValue]; } AtomString AVTrackPrivateAVFObjCImpl::language() const { if (m_assetTrack) return AtomString { languageForAVAssetTrack(m_assetTrack.get()) }; if (m_mediaSelectionOption) return AtomString { languageForAVMediaSelectionOption(m_mediaSelectionOption->avMediaSelectionOption()) }; ASSERT_NOT_REACHED(); return emptyAtom(); } String AVTrackPrivateAVFObjCImpl::languageForAVAssetTrack(AVAssetTrack* track) { NSString *language = [track extendedLanguageTag]; // If the language code is stored as a QuickTime 5-bit packed code there aren't enough bits for a full // RFC 4646 language tag so extendedLanguageTag returns NULL. In this case languageCode will return the // ISO 639-2/T language code so check it. if (!language) language = [track languageCode]; // Some legacy tracks have "und" as a language, treat that the same as no language at all. if (!language || [language isEqualToString:@"und"]) return emptyString(); return language; } String AVTrackPrivateAVFObjCImpl::languageForAVMediaSelectionOption(AVMediaSelectionOption* option) { NSString *language = [option extendedLanguageTag]; // If the language code is stored as a QuickTime 5-bit packed code there aren't enough bits for a full // RFC 4646 language tag so extendedLanguageTag returns NULL. In this case languageCode will return the // ISO 639-2/T language code so check it. if (!language) language = [[option locale] objectForKey:NSLocaleLanguageCode]; // Some legacy tracks have "und" as a language, treat that the same as no language at all. if (!language || [language isEqualToString:@"und"]) return emptyString(); return language; } PlatformVideoTrackConfiguration AVTrackPrivateAVFObjCImpl::videoTrackConfiguration() const { return { { codec() }, width(), height(), colorSpace(), framerate(), bitrate(), }; } PlatformAudioTrackConfiguration AVTrackPrivateAVFObjCImpl::audioTrackConfiguration() const { return { { codec() }, sampleRate(), numberOfChannels(), bitrate(), }; } int AVTrackPrivateAVFObjCImpl::trackID() const { if (m_assetTrack) return [m_assetTrack trackID]; if (m_mediaSelectionOption) return [[m_mediaSelectionOption->avMediaSelectionOption() optionID] intValue]; ASSERT_NOT_REACHED(); return 0; } static AVAssetTrack* assetTrackFor(const AVTrackPrivateAVFObjCImpl& impl) { if (impl.playerItemTrack() && impl.playerItemTrack().assetTrack) return impl.playerItemTrack().assetTrack; if (impl.assetTrack()) return impl.assetTrack(); if (impl.mediaSelectionOption() && impl.mediaSelectionOption()->assetTrack()) return impl.mediaSelectionOption()->assetTrack(); return nil; } static RetainPtr formatDescriptionFor(const AVTrackPrivateAVFObjCImpl& impl) { auto assetTrack = assetTrackFor(impl); if (!assetTrack || [assetTrack statusOfValueForKey:@"formatDescriptions" error:nil] != AVKeyValueStatusLoaded) return nullptr; return static_cast(assetTrack.formatDescriptions.firstObject); } String AVTrackPrivateAVFObjCImpl::codec() const { return codecFromFormatDescription(formatDescriptionFor(*this).get()); } uint32_t AVTrackPrivateAVFObjCImpl::width() const { if (auto assetTrack = assetTrackFor(*this)) return assetTrack.naturalSize.width; ASSERT_NOT_REACHED(); return 0; } uint32_t AVTrackPrivateAVFObjCImpl::height() const { if (auto assetTrack = assetTrackFor(*this)) return assetTrack.naturalSize.height; ASSERT_NOT_REACHED(); return 0; } PlatformVideoColorSpace AVTrackPrivateAVFObjCImpl::colorSpace() const { if (auto colorSpace = colorSpaceFromFormatDescription(formatDescriptionFor(*this).get())) return *colorSpace; return { }; } double AVTrackPrivateAVFObjCImpl::framerate() const { auto assetTrack = assetTrackFor(*this); if (!assetTrack) return 0; if ([assetTrack statusOfValueForKey:@"nominalFrameRate" error:nil] != AVKeyValueStatusLoaded) return 0; return assetTrack.nominalFrameRate; } uint32_t AVTrackPrivateAVFObjCImpl::sampleRate() const { auto formatDescription = formatDescriptionFor(*this); if (!formatDescription) return 0; const AudioStreamBasicDescription* const asbd = PAL::CMAudioFormatDescriptionGetStreamBasicDescription(formatDescription.get()); if (!asbd) return 0; return asbd->mSampleRate; } uint32_t AVTrackPrivateAVFObjCImpl::numberOfChannels() const { auto formatDescription = formatDescriptionFor(*this); if (!formatDescription) return 0; const AudioStreamBasicDescription* const asbd = PAL::CMAudioFormatDescriptionGetStreamBasicDescription(formatDescription.get()); if (!asbd) return 0; return asbd->mChannelsPerFrame; } uint64_t AVTrackPrivateAVFObjCImpl::bitrate() const { auto assetTrack = assetTrackFor(*this); if (!assetTrack) return 0; if ([assetTrack statusOfValueForKey:@"estimatedDataRate" error:nil] != AVKeyValueStatusLoaded) return 0; if (!std::isfinite(assetTrack.estimatedDataRate)) return 0; return assetTrack.estimatedDataRate; } } #endif // ENABLE(VIDEO)