blob: 83b642172e19e67be9eabd9a6f99abc349048f20 [file] [log] [blame]
/*
* Copyright (C) 2014 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. AND ITS CONTRIBUTORS ``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 ITS 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 "MediaSelectionGroupAVFObjC.h"
#if ENABLE(VIDEO_TRACK)
#import <AVFoundation/AVAsset.h>
#import <AVFoundation/AVMediaSelectionGroup.h>
#import <AVFoundation/AVPlayerItem.h>
#import <objc/runtime.h>
#import <wtf/Language.h>
#import <wtf/text/WTFString.h>
#if HAVE(MEDIA_ACCESSIBILITY_FRAMEWORK)
#include <MediaAccessibility/MediaAccessibility.h>
#include "MediaAccessibilitySoftLink.h"
#endif
#import <pal/cocoa/AVFoundationSoftLink.h>
namespace WebCore {
Ref<MediaSelectionOptionAVFObjC> MediaSelectionOptionAVFObjC::create(MediaSelectionGroupAVFObjC& group, AVMediaSelectionOption *option)
{
return adoptRef(*new MediaSelectionOptionAVFObjC(group, option));
}
MediaSelectionOptionAVFObjC::MediaSelectionOptionAVFObjC(MediaSelectionGroupAVFObjC& group, AVMediaSelectionOption *option)
: m_group(&group)
, m_mediaSelectionOption(option)
{
}
void MediaSelectionOptionAVFObjC::setSelected(bool selected)
{
if (!m_group)
return;
if (selected == this->selected())
return;
m_group->setSelectedOption(selected ? this : nullptr);
}
bool MediaSelectionOptionAVFObjC::selected() const
{
if (!m_group)
return false;
return this == m_group->selectedOption();
}
int MediaSelectionOptionAVFObjC::index() const
{
if (!m_group)
return 0;
return [[m_group->avMediaSelectionGroup() options] indexOfObject:m_mediaSelectionOption.get()];
}
Ref<MediaSelectionGroupAVFObjC> MediaSelectionGroupAVFObjC::create(AVPlayerItem *item, AVMediaSelectionGroup *group, const Vector<String>& characteristics)
{
return adoptRef(*new MediaSelectionGroupAVFObjC(item, group, characteristics));
}
MediaSelectionGroupAVFObjC::MediaSelectionGroupAVFObjC(AVPlayerItem *item, AVMediaSelectionGroup *group, const Vector<String>& characteristics)
: m_playerItem(item)
, m_mediaSelectionGroup(group)
, m_selectionTimer(*this, &MediaSelectionGroupAVFObjC::selectionTimerFired)
{
updateOptions(characteristics);
}
MediaSelectionGroupAVFObjC::~MediaSelectionGroupAVFObjC()
{
for (auto& option : m_options.values())
option->clearGroup();
}
void MediaSelectionGroupAVFObjC::updateOptions(const Vector<String>& characteristics)
{
RetainPtr<NSSet> newAVOptions = adoptNS([[NSSet alloc] initWithArray:[PAL::getAVMediaSelectionGroupClass() playableMediaSelectionOptionsFromArray:[m_mediaSelectionGroup options]]]);
RetainPtr<NSMutableSet> oldAVOptions = adoptNS([[NSMutableSet alloc] initWithCapacity:m_options.size()]);
for (auto& avOption : m_options.keys())
[oldAVOptions addObject:(__bridge AVMediaSelectionOption *)avOption];
RetainPtr<NSMutableSet> addedAVOptions = adoptNS([newAVOptions mutableCopy]);
[addedAVOptions minusSet:oldAVOptions.get()];
RetainPtr<NSMutableSet> removedAVOptions = adoptNS([oldAVOptions mutableCopy]);
[removedAVOptions minusSet:newAVOptions.get()];
for (AVMediaSelectionOption* removedAVOption in removedAVOptions.get()) {
if (m_selectedOption && removedAVOption == m_selectedOption->avMediaSelectionOption())
m_selectedOption = nullptr;
m_options.remove((__bridge CFTypeRef)removedAVOption);
}
ALLOW_DEPRECATED_DECLARATIONS_BEGIN
AVMediaSelectionOption* selectedOption = [m_playerItem selectedMediaOptionInMediaSelectionGroup:m_mediaSelectionGroup.get()];
ALLOW_DEPRECATED_DECLARATIONS_END
for (AVMediaSelectionOption* addedAVOption in addedAVOptions.get()) {
auto addedOption = MediaSelectionOptionAVFObjC::create(*this, addedAVOption);
if (addedAVOption == selectedOption)
m_selectedOption = addedOption.ptr();
m_options.set((__bridge CFTypeRef)addedAVOption, WTFMove(addedOption));
}
if (!m_shouldSelectOptionAutomatically)
return;
RetainPtr<NSMutableArray> nsLanguages = adoptNS([[NSMutableArray alloc] initWithCapacity:userPreferredLanguages().size()]);
for (auto& language : userPreferredLanguages())
[nsLanguages addObject:(NSString*)language];
NSArray* filteredOptions = [PAL::getAVMediaSelectionGroupClass() mediaSelectionOptionsFromArray:[m_mediaSelectionGroup options] filteredAndSortedAccordingToPreferredLanguages:nsLanguages.get()];
if (![filteredOptions count] && characteristics.isEmpty())
return;
// If no options match our language selection, search for matching characteristics across all the group's options
if (![filteredOptions count])
filteredOptions = [m_mediaSelectionGroup options];
RetainPtr<NSMutableArray> nsCharacteristics = adoptNS([[NSMutableArray alloc] initWithCapacity:characteristics.size()]);
for (auto& characteristic : characteristics)
[nsCharacteristics addObject:(NSString *)characteristic];
NSArray* optionsWithCharacteristics = [PAL::getAVMediaSelectionGroupClass() mediaSelectionOptionsFromArray:filteredOptions withMediaCharacteristics:nsCharacteristics.get()];
if (optionsWithCharacteristics && [optionsWithCharacteristics count])
filteredOptions = optionsWithCharacteristics;
if (![filteredOptions count])
return;
AVMediaSelectionOption* preferredOption = [filteredOptions objectAtIndex:0];
if (m_selectedOption && m_selectedOption->avMediaSelectionOption() == preferredOption)
return;
ASSERT(m_options.contains((__bridge CFTypeRef)preferredOption));
m_selectedOption = m_options.get((__bridge CFTypeRef)preferredOption);
m_selectionTimer.startOneShot(0_s);
}
void MediaSelectionGroupAVFObjC::setSelectedOption(MediaSelectionOptionAVFObjC* option)
{
if (m_selectedOption == option)
return;
m_shouldSelectOptionAutomatically = false;
m_selectedOption = option;
if (m_selectionTimer.isActive())
m_selectionTimer.stop();
m_selectionTimer.startOneShot(0_s);
}
void MediaSelectionGroupAVFObjC::selectionTimerFired()
{
[m_playerItem selectMediaOption:(m_selectedOption ? m_selectedOption->avMediaSelectionOption() : nil) inMediaSelectionGroup:m_mediaSelectionGroup.get()];
}
}
#endif