diff --git a/.gitignore b/.gitignore
index 3882e93a..2b1d1f9e 100644
--- a/.gitignore
+++ b/.gitignore
@@ -12,4 +12,6 @@ xcuserdata
/fastlane/*.xml
/vendor
-/.bundle
\ No newline at end of file
+/.bundle
+
+.vscode
diff --git a/Demo/Demo.xcconfig b/Demo/Demo.xcconfig
index 2ab36254..0a07d918 100644
--- a/Demo/Demo.xcconfig
+++ b/Demo/Demo.xcconfig
@@ -1,5 +1,5 @@
// Version information
-MARKETING_VERSION = 7.0.1
+MARKETING_VERSION = 7.0.2
// Deployment targets
IPHONEOS_DEPLOYMENT_TARGET = 9.0
diff --git a/Demo/Resources/Data/MultiPlayerDemoConfiguration.plist b/Demo/Resources/Data/MultiPlayerDemoConfiguration.plist
index f1fb71f2..9da8b380 100644
--- a/Demo/Resources/Data/MultiPlayerDemoConfiguration.plist
+++ b/Demo/Resources/Data/MultiPlayerDemoConfiguration.plist
@@ -14,7 +14,7 @@
name
Livestream
url
- https://rtsc3video-lh.akamaihd.net/i/rtsc3video_ww@513975/master.m3u8?dw=0
+ https://rtsc3video.akamaized.net/hls/live/2042837/c3video/3/playlist.m3u8?dw=0
name
diff --git a/Demo/Resources/Data/SegmentDemoConfiguration.plist b/Demo/Resources/Data/SegmentDemoConfiguration.plist
index 035b7713..27c18359 100644
--- a/Demo/Resources/Data/SegmentDemoConfiguration.plist
+++ b/Demo/Resources/Data/SegmentDemoConfiguration.plist
@@ -363,7 +363,7 @@
name
Segments in DVR stream
url
- https://rtsc3video-lh.akamaihd.net/i/rtsc3video_ww@513975/master.m3u8
+ https://rtsc3video.akamaized.net/hls/live/2042837/c3video/3/playlist.m3u8
segments
diff --git a/Demo/Resources/Data/VideoDemoConfiguration.plist b/Demo/Resources/Data/VideoDemoConfiguration.plist
index 0abb681a..ef0d8d15 100644
--- a/Demo/Resources/Data/VideoDemoConfiguration.plist
+++ b/Demo/Resources/Data/VideoDemoConfiguration.plist
@@ -8,13 +8,13 @@
name
Livestream
url
- https://rtsc3video-lh.akamaihd.net/i/rtsc3video_ww@513975/master.m3u8?dw=0
+ https://rtsc3video.akamaized.net/hls/live/2042837/c3video/3/playlist.m3u8?dw=0
name
Livestream with DVR
url
- https://rtsc3video-lh.akamaihd.net/i/rtsc3video_ww@513975/master.m3u8
+ https://rtsc3video.akamaized.net/hls/live/2042837/c3video/3/playlist.m3u8
name
@@ -62,7 +62,7 @@
name
How women are taking on the Cresta Run
url
- https://srgplayerswivod-vh.akamaihd.net/i/44785866/,video,.mp4.csmil/master.m3u8
+ https://swi-vod.akamaized.net/videoJson/44785866/master.m3u8
name
@@ -82,7 +82,7 @@
name
- 10vor10 subject with buggy subtitles (Akamai issue)
+ 10vor10 subject with buggy subtitles (Akamai MLS 3 issue)
url
https://hdvodsrforigins3-vh.akamaihd.net/i/assets/video/10vor10/2020/06/10vor10_20200610_215034_21058070_v_webcast_h264_,q40,q10,q20,q30,q50,.mp4.csmil/master.m3u8?start=66.4&end=554.52&caption=srf%2F26a1de9a-7b3e-460f-a3c2-24421b00217a%2Fepisode%2Fde%2Fvod%2Fvod.m3u8%3Ade%3ADeutsch%3Asdh&webvttbaseurl=www.srf.ch%2Fsubtitles
diff --git a/Gemfile.lock b/Gemfile.lock
index 80005d6c..ba734c7e 100644
--- a/Gemfile.lock
+++ b/Gemfile.lock
@@ -8,20 +8,20 @@ GEM
artifactory (3.0.15)
atomos (0.1.3)
aws-eventstream (1.2.0)
- aws-partitions (1.554.0)
- aws-sdk-core (3.126.0)
+ aws-partitions (1.597.0)
+ aws-sdk-core (3.131.1)
aws-eventstream (~> 1, >= 1.0.2)
aws-partitions (~> 1, >= 1.525.0)
aws-sigv4 (~> 1.1)
- jmespath (~> 1.0)
- aws-sdk-kms (1.54.0)
- aws-sdk-core (~> 3, >= 3.126.0)
+ jmespath (~> 1, >= 1.6.1)
+ aws-sdk-kms (1.57.0)
+ aws-sdk-core (~> 3, >= 3.127.0)
aws-sigv4 (~> 1.1)
- aws-sdk-s3 (1.112.0)
- aws-sdk-core (~> 3, >= 3.126.0)
+ aws-sdk-s3 (1.114.0)
+ aws-sdk-core (~> 3, >= 3.127.0)
aws-sdk-kms (~> 1)
aws-sigv4 (~> 1.4)
- aws-sigv4 (1.4.0)
+ aws-sigv4 (1.5.0)
aws-eventstream (~> 1, >= 1.0.2)
babosa (1.0.4)
claide (1.0.3)
@@ -36,8 +36,8 @@ GEM
unf (>= 0.0.5, < 1.0.0)
dotenv (2.7.6)
emoji_regex (3.2.3)
- excon (0.91.0)
- faraday (1.9.3)
+ excon (0.92.3)
+ faraday (1.10.0)
faraday-em_http (~> 1.0)
faraday-em_synchrony (~> 1.0)
faraday-excon (~> 1.1)
@@ -56,8 +56,8 @@ GEM
faraday-em_synchrony (1.0.0)
faraday-excon (1.1.0)
faraday-httpclient (1.0.1)
- faraday-multipart (1.0.3)
- multipart-post (>= 1.2, < 3)
+ faraday-multipart (1.0.4)
+ multipart-post (~> 2)
faraday-net_http (1.0.1)
faraday-net_http_persistent (1.2.0)
faraday-patron (1.0.0)
@@ -66,7 +66,7 @@ GEM
faraday_middleware (1.2.0)
faraday (~> 1.0)
fastimage (2.2.6)
- fastlane (2.204.3)
+ fastlane (2.206.2)
CFPropertyList (>= 2.3, < 4.0.0)
addressable (>= 2.8, < 3.0.0)
artifactory (~> 3.0)
@@ -106,9 +106,9 @@ GEM
xcpretty (~> 0.3.0)
xcpretty-travis-formatter (>= 0.0.3)
gh_inspector (1.1.3)
- google-apis-androidpublisher_v3 (0.16.0)
+ google-apis-androidpublisher_v3 (0.21.0)
google-apis-core (>= 0.4, < 2.a)
- google-apis-core (0.4.2)
+ google-apis-core (0.5.0)
addressable (~> 2.5, >= 2.5.1)
googleauth (>= 0.16.2, < 2.a)
httpclient (>= 2.8.1, < 3.a)
@@ -121,15 +121,15 @@ GEM
google-apis-core (>= 0.4, < 2.a)
google-apis-playcustomapp_v1 (0.7.0)
google-apis-core (>= 0.4, < 2.a)
- google-apis-storage_v1 (0.11.0)
+ google-apis-storage_v1 (0.14.0)
google-apis-core (>= 0.4, < 2.a)
google-cloud-core (1.6.0)
google-cloud-env (~> 1.0)
google-cloud-errors (~> 1.0)
- google-cloud-env (1.5.0)
- faraday (>= 0.17.3, < 2.0)
+ google-cloud-env (1.6.0)
+ faraday (>= 0.17.3, < 3.0)
google-cloud-errors (1.2.0)
- google-cloud-storage (1.36.1)
+ google-cloud-storage (1.36.2)
addressable (~> 2.8)
digest-crc (~> 0.4)
google-apis-iamcredentials_v1 (~> 0.1)
@@ -137,20 +137,20 @@ GEM
google-cloud-core (~> 1.6)
googleauth (>= 0.16.2, < 2.a)
mini_mime (~> 1.0)
- googleauth (1.1.1)
- faraday (>= 0.17.3, < 2.0)
+ googleauth (1.1.3)
+ faraday (>= 0.17.3, < 3.a)
jwt (>= 1.4, < 3.0)
memoist (~> 0.16)
multi_json (~> 1.11)
os (>= 0.9, < 2.0)
signet (>= 0.16, < 2.a)
highline (2.0.3)
- http-cookie (1.0.4)
+ http-cookie (1.0.5)
domain_name (~> 0.5)
httpclient (2.8.3)
- jmespath (1.6.0)
- json (2.6.1)
- jwt (2.3.0)
+ jmespath (1.6.1)
+ json (2.6.2)
+ jwt (2.4.1)
memoist (0.16.2)
mini_magick (4.11.0)
mini_mime (1.1.2)
@@ -161,9 +161,9 @@ GEM
optparse (0.1.1)
os (1.1.4)
plist (3.6.0)
- public_suffix (4.0.6)
+ public_suffix (4.0.7)
rake (13.0.6)
- representable (3.1.1)
+ representable (3.2.0)
declarative (< 0.1.0)
trailblazer-option (>= 0.1.1, < 0.2.0)
uber (< 0.2.0)
@@ -173,9 +173,9 @@ GEM
ruby2_keywords (0.0.5)
rubyzip (2.3.2)
security (0.1.3)
- signet (0.16.0)
+ signet (0.16.1)
addressable (~> 2.8)
- faraday (>= 0.17.3, < 2.0)
+ faraday (>= 0.17.5, < 3.0)
jwt (>= 1.5, < 3.0)
multi_json (~> 1.10)
simctl (1.6.8)
@@ -192,7 +192,7 @@ GEM
uber (0.1.0)
unf (0.1.4)
unf_ext
- unf_ext (0.0.8)
+ unf_ext (0.0.8.2)
unicode-display_width (1.8.0)
webrick (1.7.0)
word_wrap (1.0.0)
diff --git a/Package.swift b/Package.swift
index 00f034b8..6862278c 100644
--- a/Package.swift
+++ b/Package.swift
@@ -3,7 +3,7 @@
import PackageDescription
struct ProjectSettings {
- static let marketingVersion: String = "7.0.1"
+ static let marketingVersion: String = "7.0.2"
}
let package = Package(
diff --git a/Sources/SRGMediaPlayer/SRGMediaPlayerController.m b/Sources/SRGMediaPlayer/SRGMediaPlayerController.m
index 2a3fdc28..e99b031e 100644
--- a/Sources/SRGMediaPlayer/SRGMediaPlayerController.m
+++ b/Sources/SRGMediaPlayer/SRGMediaPlayerController.m
@@ -52,7 +52,8 @@ static CMTime SRGSafeStartSeekOffset(void)
static AVMediaSelectionOption *SRGMediaPlayerControllerAutomaticAudioDefaultOption(NSArray *audioOptions);
static AVMediaSelectionOption *SRGMediaPlayerControllerAutomaticSubtitleDefaultOption(NSArray *subtitleOptions, AVMediaSelectionOption *audioOption);
static AVMediaSelectionOption *SRGMediaPlayerControllerSubtitleDefaultOption(NSArray *subtitleOptions, AVMediaSelectionOption *audioOption);
-static AVMediaSelectionOption *SRGMediaPlayerControllerSubtitleDefaultLanguageOption(NSArray *subtitleOptions, NSString *language, NSArray *characteristics);
+static AVMediaSelectionOption *SRGMediaPlayerControllerSubtitleDefaultAlwaysOnLanguageOption(NSArray *subtitleOptions, NSString *language, NSArray *characteristics, AVMediaSelectionOption *audioOption);
+static AVMediaSelectionOption *SRGMediaPlayerControllerSubtitleForcedLanguageOption(NSArray *subtitleOptions, AVMediaSelectionOption *audioOption);
@interface SRGMediaPlayerController () {
@private
@@ -1539,7 +1540,7 @@ - (void)reloadMediaConfiguration
// Setup subtitles. The value `nil` is allowed to disable subtitles entirely.
AVMediaSelectionGroup *subtitleGroup = [asset mediaSelectionGroupForMediaCharacteristic:AVMediaCharacteristicLegible];
if (subtitleGroup) {
- NSArray *subtitleOptions = [AVMediaSelectionGroup mediaSelectionOptionsFromArray:subtitleGroup.srgmediaplayer_languageOptions withoutMediaCharacteristics:@[AVMediaCharacteristicContainsOnlyForcedSubtitles]];
+ NSArray *subtitleOptions = subtitleGroup.srgmediaplayer_languageOptions;
AVMediaSelectionOption *defaultSubtitleOption = SRGMediaPlayerControllerSubtitleDefaultOption(subtitleOptions, audioOption);
AVMediaSelectionOption *subtitleOption = self.subtitleConfigurationBlock ? self.subtitleConfigurationBlock(subtitleOptions, audioOption, defaultSubtitleOption) : defaultSubtitleOption;
[playerItem selectMediaOption:subtitleOption inMediaSelectionGroup:subtitleGroup];
@@ -1809,8 +1810,8 @@ - (AVMediaSelectionOption *)selectedOptionForPlayer:(AVPlayer *)player withMedia
return nil;
}
- AVMediaSelectionGroup *audioGroup = [asset mediaSelectionGroupForMediaCharacteristic:mediaCharacteristic];
- return audioGroup ? [playerItem srgmediaplayer_selectedMediaOptionInMediaSelectionGroup:audioGroup] : nil;
+ AVMediaSelectionGroup *group = [asset mediaSelectionGroupForMediaCharacteristic:mediaCharacteristic];
+ return group ? [playerItem srgmediaplayer_selectedMediaOptionInMediaSelectionGroup:group] : nil;
}
- (void)updateTracksForPlayer:(AVPlayer *)player
@@ -1865,18 +1866,44 @@ - (void)selectMediaOption:(AVMediaSelectionOption *)option inMediaSelectionGroup
return;
}
- [playerItem selectMediaOption:option inMediaSelectionGroup:group];
-
- // If Automatic has been set for subtitles, changing the audio must update the subtitles accordingly
- MACaptionAppearanceDisplayType displayType = MACaptionAppearanceGetDisplayType(kMACaptionAppearanceDomainUser);
- if ([characteristic isEqualToString:AVMediaCharacteristicAudible] && displayType == kMACaptionAppearanceDisplayTypeAutomatic) {
- // Provide the selected audio option as context information, so that update is consistent when using AirPlay as well
- // (we cannot use `-selectMediaOptionAutomaticallyInMediaSelectionGroupWithCharacteristic:`) as the audio selection
- // takes more time over AirPlay, yielding the old value for a short while.
+ if ([characteristic isEqualToString:AVMediaCharacteristicAudible]) {
+ AVMediaSelectionOption *audioOption = option ?: SRGMediaPlayerControllerAutomaticAudioDefaultOption(group.options);
+ [playerItem selectMediaOption:audioOption inMediaSelectionGroup:group];
+
+ // Update subtitles to match the audio track if needed.
AVMediaSelectionGroup *subtitleGroup = [asset mediaSelectionGroupForMediaCharacteristic:AVMediaCharacteristicLegible];
if (subtitleGroup) {
- AVMediaSelectionOption *subtitleOption = SRGMediaPlayerControllerAutomaticSubtitleDefaultOption(subtitleGroup.srgmediaplayer_languageOptions, option);
- [playerItem selectMediaOption:subtitleOption inMediaSelectionGroup:subtitleGroup];
+ // Provide the selected audio option as context information, so that update is consistent when using AirPlay as well
+ // (we cannot use `-selectMediaOptionAutomaticallyInMediaSelectionGroupWithCharacteristic:`) as the audio selection
+ // takes more time over AirPlay, yielding the old value for a short while.
+ MACaptionAppearanceDisplayType displayType = MACaptionAppearanceGetDisplayType(kMACaptionAppearanceDomainUser);
+ switch (displayType) {
+ case kMACaptionAppearanceDisplayTypeAutomatic: {
+ AVMediaSelectionOption *subtitleOption = SRGMediaPlayerControllerAutomaticSubtitleDefaultOption(subtitleGroup.srgmediaplayer_languageOptions, audioOption);
+ [playerItem selectMediaOption:subtitleOption inMediaSelectionGroup:subtitleGroup];
+ break;
+ }
+
+ case kMACaptionAppearanceDisplayTypeForcedOnly: {
+ AVMediaSelectionOption *subtitleOption = SRGMediaPlayerControllerSubtitleForcedLanguageOption(subtitleGroup.srgmediaplayer_languageOptions, audioOption);
+ [playerItem selectMediaOption:subtitleOption inMediaSelectionGroup:subtitleGroup];
+ break;
+ }
+
+ case kMACaptionAppearanceDisplayTypeAlwaysOn: {
+ break;
+ }
+ }
+ }
+ }
+ else if ([characteristic isEqualToString:AVMediaCharacteristicLegible]) {
+ if (option) {
+ [playerItem selectMediaOption:option inMediaSelectionGroup:group];
+ }
+ else {
+ AVMediaSelectionOption *audioOption = [self selectedMediaOptionInMediaSelectionGroupWithCharacteristic:AVMediaCharacteristicAudible];
+ AVMediaSelectionOption *subtitleOption = SRGMediaPlayerControllerSubtitleForcedLanguageOption(group.options, audioOption);
+ [playerItem selectMediaOption:subtitleOption inMediaSelectionGroup:group];
}
}
@@ -1935,19 +1962,21 @@ - (BOOL)matchesAutomaticSubtitleSelection
return NO;
}
- AVMediaSelectionOption *audioOption = [self selectedMediaOptionInMediaSelectionGroupWithCharacteristic:AVMediaCharacteristicAudible];
AVMediaSelectionGroup *subtitleGroup = [asset mediaSelectionGroupForMediaCharacteristic:AVMediaCharacteristicLegible];
if (! subtitleGroup) {
return NO;
}
+ NSArray *subtitleOptions = subtitleGroup.srgmediaplayer_languageOptions;
+ AVMediaSelectionOption *audioOption = [self selectedMediaOptionInMediaSelectionGroupWithCharacteristic:AVMediaCharacteristicAudible];
+ AVMediaSelectionOption *defaultSubtitleOption = SRGMediaPlayerControllerAutomaticSubtitleDefaultOption(subtitleOptions, audioOption);
AVMediaSelectionOption *subtitleOption = [playerItem srgmediaplayer_selectedMediaOptionInMediaSelectionGroup:subtitleGroup];
- AVMediaSelectionOption *defaultSubtitleOption = SRGMediaPlayerControllerAutomaticSubtitleDefaultOption(subtitleGroup.srgmediaplayer_languageOptions, audioOption);
if (defaultSubtitleOption) {
return [defaultSubtitleOption isEqual:subtitleOption];
}
else {
- return ! subtitleOption || [subtitleOption hasMediaCharacteristic:AVMediaCharacteristicContainsOnlyForcedSubtitles];
+ AVMediaSelectionOption *forcedSubtitleOption = SRGMediaPlayerControllerSubtitleForcedLanguageOption(subtitleOptions, audioOption);
+ return ! subtitleOption || [subtitleOption isEqual:forcedSubtitleOption];
}
}
@@ -2333,22 +2362,23 @@ - (NSString *)description
return [AVMediaSelectionGroup mediaSelectionOptionsFromArray:options withMediaCharacteristics:characteristics].firstObject ?: options.firstObject;
}
-// For Automatic mode, return the default subtitle option which should be selected in the provided list (an audio option can be provided to help find the best match).
+// For Automatic mode return the default subtitle option which should be selected in the provided list (an audio option can be provided to help find the best match).
static AVMediaSelectionOption *SRGMediaPlayerControllerAutomaticSubtitleDefaultOption(NSArray *subtitleOptions, AVMediaSelectionOption *audioOption)
{
NSCParameterAssert(subtitleOptions);
NSString *audioLanguage = [audioOption.locale objectForKey:NSLocaleLanguageCode];
NSString *applicationLanguage = SRGMediaPlayerApplicationLocalization();
- NSArray *characteristics = CFBridgingRelease(MACaptionAppearanceCopyPreferredCaptioningMediaCharacteristics(kMACaptionAppearanceDomainUser));
- if (characteristics.count != 0
- || (audioLanguage && ! [audioLanguage isEqualToString:applicationLanguage])) {
- return SRGMediaPlayerControllerSubtitleDefaultLanguageOption(subtitleOptions, applicationLanguage, characteristics);
- }
- else {
- return nil;
+ if (audioLanguage && ! [audioLanguage isEqualToString:applicationLanguage]) {
+ NSArray *characteristics = CFBridgingRelease(MACaptionAppearanceCopyPreferredCaptioningMediaCharacteristics(kMACaptionAppearanceDomainUser));
+ AVMediaSelectionOption *subtitleOption = SRGMediaPlayerControllerSubtitleDefaultAlwaysOnLanguageOption(subtitleOptions, applicationLanguage, characteristics, audioOption);
+ if (subtitleOption) {
+ return subtitleOption;
+ }
}
+
+ return SRGMediaPlayerControllerSubtitleForcedLanguageOption(subtitleOptions, audioOption);
}
// Return the default subtitle option which should be selected in the provided list, based on on `MediaAccessibility` settings (an audio option can be provided to help
@@ -2367,31 +2397,50 @@ - (NSString *)description
case kMACaptionAppearanceDisplayTypeAlwaysOn: {
NSString *lastSelectedLanguage = SRGMediaAccessibilityCaptionAppearanceLastSelectedLanguage(kMACaptionAppearanceDomainUser);
NSArray *characteristics = CFBridgingRelease(MACaptionAppearanceCopyPreferredCaptioningMediaCharacteristics(kMACaptionAppearanceDomainUser));
- return SRGMediaPlayerControllerSubtitleDefaultLanguageOption(subtitleOptions, lastSelectedLanguage, characteristics);
+ return SRGMediaPlayerControllerSubtitleDefaultAlwaysOnLanguageOption(subtitleOptions, lastSelectedLanguage, characteristics, audioOption);
break;
}
- default: {
- return nil;
+ case kMACaptionAppearanceDisplayTypeForcedOnly: {
+ return SRGMediaPlayerControllerSubtitleForcedLanguageOption(subtitleOptions, audioOption);
break;
}
}
}
-// Return the default subtitle option which should be selected in the provided list, matching a specific language and characteristics.
-static AVMediaSelectionOption *SRGMediaPlayerControllerSubtitleDefaultLanguageOption(NSArray *subtitleOptions, NSString *language, NSArray *characteristics)
+// Return the default "Always on" subtitle option which should be selected in the provided list, matching a specific language and characteristics.
+static AVMediaSelectionOption *SRGMediaPlayerControllerSubtitleDefaultAlwaysOnLanguageOption(NSArray *subtitleOptions, NSString *language, NSArray *characteristics, AVMediaSelectionOption *audioOption)
{
NSPredicate *predicate = [NSPredicate predicateWithBlock:^BOOL(AVMediaSelectionOption * _Nullable option, NSDictionary * _Nullable bindings) {
return [[option.locale objectForKey:NSLocaleLanguageCode] isEqualToString:language];
}];
- NSArray *options = [[AVMediaSelectionGroup mediaSelectionOptionsFromArray:subtitleOptions withoutMediaCharacteristics:@[AVMediaCharacteristicContainsOnlyForcedSubtitles]] filteredArrayUsingPredicate:predicate];
- // Attempt to find a better match depending on the provided characteristics
- if (characteristics.count != 0) {
- return [AVMediaSelectionGroup mediaSelectionOptionsFromArray:options withMediaCharacteristics:characteristics].firstObject ?: options.firstObject;
+ // Only consider unforced subtitles matching the desired language
+ NSArray *alwaysOnOptions = [[AVMediaSelectionGroup mediaSelectionOptionsFromArray:subtitleOptions withoutMediaCharacteristics:@[AVMediaCharacteristicContainsOnlyForcedSubtitles]] filteredArrayUsingPredicate:predicate];
+
+ // Attempt to find an exact match for the provided characteristics
+ AVMediaSelectionOption *exactOption = [AVMediaSelectionGroup mediaSelectionOptionsFromArray:alwaysOnOptions withMediaCharacteristics:characteristics].firstObject;
+ if (exactOption) {
+ return exactOption;
}
- // No characteristics provided. At least attempt to avoid closed captions
- else {
- return [AVMediaSelectionGroup mediaSelectionOptionsFromArray:options withoutMediaCharacteristics:@[AVMediaCharacteristicTranscribesSpokenDialogForAccessibility]].firstObject ?: options.firstObject;
+
+ // Ignore characteristics. Accessibility settings like SDH preference will be ignored but having a subtitle track
+ // as a fallback is still better than having no track at all.
+ AVMediaSelectionOption *fallbackOption = alwaysOnOptions.firstObject;
+ if (fallbackOption) {
+ return fallbackOption;
}
+
+ // No match found. Use forced subtitles matching the audio language.
+ return SRGMediaPlayerControllerSubtitleForcedLanguageOption(subtitleOptions, audioOption);
+}
+
+// Return the forced subtitle option having some language in the provided list, `nil` if none
+static AVMediaSelectionOption *SRGMediaPlayerControllerSubtitleForcedLanguageOption(NSArray *subtitleOptions, AVMediaSelectionOption *audioOption)
+{
+ NSString *audioLanguage = [audioOption.locale objectForKey:NSLocaleLanguageCode];
+ NSPredicate *predicate = [NSPredicate predicateWithBlock:^BOOL(AVMediaSelectionOption * _Nullable option, NSDictionary * _Nullable bindings) {
+ return [[option.locale objectForKey:NSLocaleLanguageCode] isEqualToString:audioLanguage];
+ }];
+ return [[AVMediaSelectionGroup mediaSelectionOptionsFromArray:subtitleOptions withMediaCharacteristics:@[AVMediaCharacteristicContainsOnlyForcedSubtitles]] filteredArrayUsingPredicate:predicate].firstObject;
}
diff --git a/Sources/SRGMediaPlayer/SRGPlaybackSettingsViewController~ios.m b/Sources/SRGMediaPlayer/SRGPlaybackSettingsViewController~ios.m
index 30eec294..17fec889 100755
--- a/Sources/SRGMediaPlayer/SRGPlaybackSettingsViewController~ios.m
+++ b/Sources/SRGMediaPlayer/SRGPlaybackSettingsViewController~ios.m
@@ -587,7 +587,12 @@ - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(N
cell = [self subtitleCellForTableView:tableView];
AVMediaSelectionOption *selectedOption = [self.mediaPlayerController selectedMediaOptionInMediaSelectionGroupWithCharacteristic:AVMediaCharacteristicLegible];
- cell.detailTextLabel.text = SRGHintForMediaSelectionOption(selectedOption) ?: SRGMediaPlayerLocalizedString(@"None", @"Label displayed when no subtitles have been selected in automatic mode");
+ if (! [selectedOption hasMediaCharacteristic:AVMediaCharacteristicContainsOnlyForcedSubtitles]) {
+ cell.detailTextLabel.text = SRGHintForMediaSelectionOption(selectedOption);
+ }
+ else {
+ cell.detailTextLabel.text = nil;
+ }
}
else {
cell = [self defaultCellForTableView:tableView];
diff --git a/Tests/SRGMediaPlayerTests/PlaybackTestCase.m b/Tests/SRGMediaPlayerTests/PlaybackTestCase.m
index 1d0a6d41..75b96141 100644
--- a/Tests/SRGMediaPlayerTests/PlaybackTestCase.m
+++ b/Tests/SRGMediaPlayerTests/PlaybackTestCase.m
@@ -23,12 +23,12 @@
static NSURL *LiveTestURL(void)
{
- return [NSURL URLWithString:@"https://rtsc3video-lh.akamaihd.net/i/rtsc3video_ww@513975/master.m3u8?dw=0"];
+ return [NSURL URLWithString:@"https://rtsc3video.akamaized.net/hls/live/2042837/c3video/3/playlist.m3u8?dw=0"];
}
static NSURL *DVRNoTimestampTestURL(void)
{
- return [NSURL URLWithString:@"https://rtsc3video-lh.akamaihd.net/i/rtsc3video_ww@513975/master.m3u8"];
+ return [NSURL URLWithString:@"https://lsaplus.swisstxt.ch/audio/la-1ere_96.stream/playlist.m3u8"];
}
static NSURL *DVRTimestampTestURL(void)
@@ -944,7 +944,8 @@ - (void)testPauseAtStreamEnd
}];
}
-- (void)testFixedStreamEndWithBuggyAkamaiStreamWithSubtitles
+// TODO: Remove when Akamai MSL 3 at SRF is fully removed and try with new stream packaging solution (clipped videos)
+- (void)testFixedStreamEndWithBuggyAkamaiMLS3StreamWithSubtitles
{
[self expectationForSingleNotification:SRGMediaPlayerPlaybackStateDidChangeNotification object:self.mediaPlayerController handler:^BOOL(NSNotification * _Nonnull notification) {
return [notification.userInfo[SRGMediaPlayerPlaybackStateKey] integerValue] == SRGMediaPlayerPlaybackStatePlaying;
@@ -1069,6 +1070,8 @@ - (void)testLiveProperties
self.mediaPlayerController.minimumDVRWindowLength = 40.;
}
+ self.mediaPlayerController.minimumDVRWindowLength = 40.;
+
[self expectationForPredicate:[NSPredicate predicateWithBlock:^BOOL(SRGMediaPlayerController * _Nullable mediaPlayerController, NSDictionary * _Nullable bindings) {
return mediaPlayerController.streamType != SRGMediaPlayerStreamTypeUnknown;
}] evaluatedWithObject:self.mediaPlayerController handler:nil];
@@ -1079,7 +1082,6 @@ - (void)testLiveProperties
XCTAssertEqual(self.mediaPlayerController.streamType, SRGMediaPlayerStreamTypeLive);
XCTAssertTrue(self.mediaPlayerController.live);
- XCTAssertTrue([NSDate.date timeIntervalSinceDate:self.mediaPlayerController.currentDate] < 1.);
[self expectationForSingleNotification:SRGMediaPlayerPlaybackStateDidChangeNotification object:self.mediaPlayerController handler:^BOOL(NSNotification * _Nonnull notification) {
return [notification.userInfo[SRGMediaPlayerPlaybackStateKey] integerValue] == SRGMediaPlayerPlaybackStateIdle;
@@ -3082,7 +3084,7 @@ - (void)testDVRStreamEndAbsoluteTolerance
return [notification.userInfo[SRGMediaPlayerPlaybackStateKey] integerValue] == SRGMediaPlayerPlaybackStatePlaying;
}];
- [self.mediaPlayerController playURL:DVRNoTimestampTestURL() atPosition:nil withSegments:nil userInfo:nil];
+ [self.mediaPlayerController playURL:DVRTimestampTestURL() atPosition:nil withSegments:nil userInfo:nil];
[self waitForExpectationsWithTimeout:30. handler:nil];
diff --git a/Tests/SRGMediaPlayerTests/RateTestCase.m b/Tests/SRGMediaPlayerTests/RateTestCase.m
index 99f20373..d12ac022 100644
--- a/Tests/SRGMediaPlayerTests/RateTestCase.m
+++ b/Tests/SRGMediaPlayerTests/RateTestCase.m
@@ -16,12 +16,12 @@
static NSURL *LiveTestURL(void)
{
- return [NSURL URLWithString:@"https://rtsc3video-lh.akamaihd.net/i/rtsc3video_ww@513975/master.m3u8?dw=0"];
+ return [NSURL URLWithString:@"https://rtsc3video.akamaized.net/hls/live/2042837/c3video/3/playlist.m3u8?dw=0"];
}
static NSURL *DVRTestURL(void)
{
- return [NSURL URLWithString:@"https://rtsc3video-lh.akamaihd.net/i/rtsc3video_ww@513975/master.m3u8"];
+ return [NSURL URLWithString:@"https://rtsc3video.akamaized.net/hls/live/2042837/c3video/3/playlist.m3u8"];
}
@import SRGMediaPlayer;
diff --git a/Tests/SRGMediaPlayerTests/TracksTestCase.m b/Tests/SRGMediaPlayerTests/TracksTestCase.m
index 8a6be0e1..20f60c5d 100644
--- a/Tests/SRGMediaPlayerTests/TracksTestCase.m
+++ b/Tests/SRGMediaPlayerTests/TracksTestCase.m
@@ -193,6 +193,130 @@ - (void)testSubtitlesNotificationsWithSubtitleConfiguration
[self waitForExpectationsWithTimeout:30. handler:nil];
}
+- (void)testForcedOnlyBehaviorWithForcedSubtitles
+{
+ MACaptionAppearanceSetDisplayType(kMACaptionAppearanceDomainUser, kMACaptionAppearanceDisplayTypeForcedOnly);
+
+ [self expectationForSingleNotification:SRGMediaPlayerSubtitleTrackDidChangeNotification object:self.mediaPlayerController handler:^BOOL(NSNotification * _Nonnull notification) {
+ XCTAssertNil(notification.userInfo[SRGMediaPlayerPreviousTrackKey]);
+
+ AVMediaSelectionOption *subtitleOption = notification.userInfo[SRGMediaPlayerTrackKey];
+ XCTAssertEqualObjects([subtitleOption.locale objectForKey:NSLocaleLanguageCode], @"en");
+ XCTAssertTrue([subtitleOption hasMediaCharacteristic:AVMediaCharacteristicLegible]);
+ XCTAssertTrue([subtitleOption hasMediaCharacteristic:AVMediaCharacteristicContainsOnlyForcedSubtitles]);
+ return YES;
+ }];
+
+ [self.mediaPlayerController playURL:InternationalTracksOnDemandTestURL()];
+
+ [self waitForExpectationsWithTimeout:30. handler:nil];
+
+ XCTAssertEqualObjects([self selectedLanguageCodeInMediaSelectionGroupWithCharacteristic:AVMediaCharacteristicLegible], @"en");
+}
+
+- (void)testAutomaticBehaviorWithForcedSubtitles
+{
+ MACaptionAppearanceSetDisplayType(kMACaptionAppearanceDomainUser, kMACaptionAppearanceDisplayTypeAutomatic);
+
+ [self expectationForSingleNotification:SRGMediaPlayerSubtitleTrackDidChangeNotification object:self.mediaPlayerController handler:^BOOL(NSNotification * _Nonnull notification) {
+ XCTAssertNil(notification.userInfo[SRGMediaPlayerPreviousTrackKey]);
+
+ AVMediaSelectionOption *subtitleOption = notification.userInfo[SRGMediaPlayerTrackKey];
+ XCTAssertEqualObjects([subtitleOption.locale objectForKey:NSLocaleLanguageCode], @"en");
+ XCTAssertTrue([subtitleOption hasMediaCharacteristic:AVMediaCharacteristicLegible]);
+ XCTAssertTrue([subtitleOption hasMediaCharacteristic:AVMediaCharacteristicContainsOnlyForcedSubtitles]);
+ return YES;
+ }];
+
+ [self.mediaPlayerController playURL:InternationalTracksOnDemandTestURL()];
+
+ [self waitForExpectationsWithTimeout:30. handler:nil];
+
+ XCTAssertEqualObjects([self selectedLanguageCodeInMediaSelectionGroupWithCharacteristic:AVMediaCharacteristicLegible], @"en");
+}
+
+- (void)testAlwaysOnBehaviorWithForcedSubtitles
+{
+ MACaptionAppearanceSetDisplayType(kMACaptionAppearanceDomainUser, kMACaptionAppearanceDisplayTypeAlwaysOn);
+ MACaptionAppearanceAddSelectedLanguage(kMACaptionAppearanceDomainUser, (__bridge CFStringRef _Nonnull)@"en");
+
+ [self expectationForSingleNotification:SRGMediaPlayerSubtitleTrackDidChangeNotification object:self.mediaPlayerController handler:^BOOL(NSNotification * _Nonnull notification) {
+ XCTAssertNil(notification.userInfo[SRGMediaPlayerPreviousTrackKey]);
+
+ AVMediaSelectionOption *subtitleOption = notification.userInfo[SRGMediaPlayerTrackKey];
+ XCTAssertEqualObjects([subtitleOption.locale objectForKey:NSLocaleLanguageCode], @"en");
+ XCTAssertTrue([subtitleOption hasMediaCharacteristic:AVMediaCharacteristicLegible]);
+ XCTAssertFalse([subtitleOption hasMediaCharacteristic:AVMediaCharacteristicContainsOnlyForcedSubtitles]);
+ return YES;
+ }];
+
+ [self.mediaPlayerController playURL:InternationalTracksOnDemandTestURL()];
+
+ [self waitForExpectationsWithTimeout:30. handler:nil];
+
+ XCTAssertEqualObjects([self selectedLanguageCodeInMediaSelectionGroupWithCharacteristic:AVMediaCharacteristicLegible], @"en");
+}
+
+- (void)testForcedOnlyBehaviorWithoutForcedSubtitles
+{
+ MACaptionAppearanceSetDisplayType(kMACaptionAppearanceDomainUser, kMACaptionAppearanceDisplayTypeForcedOnly);
+
+ [self expectationForElapsedTimeInterval:2. withHandler:nil];
+
+ id subtitleTrackObserver = [NSNotificationCenter.defaultCenter addObserverForName:SRGMediaPlayerSubtitleTrackDidChangeNotification object:self.mediaPlayerController queue:nil usingBlock:^(NSNotification * _Nonnull note) {
+ XCTFail(@"No subtitle track change is expected");
+ }];
+
+ [self.mediaPlayerController playURL:SwissTracksOnDemandTestURL()];
+
+ [self waitForExpectationsWithTimeout:30. handler:^(NSError * _Nullable error) {
+ [NSNotificationCenter.defaultCenter removeObserver:subtitleTrackObserver];
+ }];
+
+ XCTAssertNil([self selectedLanguageCodeInMediaSelectionGroupWithCharacteristic:AVMediaCharacteristicLegible]);
+}
+
+- (void)testAutomaticBehaviorWithoutForcedSubtitles
+{
+ MACaptionAppearanceSetDisplayType(kMACaptionAppearanceDomainUser, kMACaptionAppearanceDisplayTypeAutomatic);
+
+ [self expectationForElapsedTimeInterval:2. withHandler:nil];
+
+ id subtitleTrackObserver = [NSNotificationCenter.defaultCenter addObserverForName:SRGMediaPlayerSubtitleTrackDidChangeNotification object:self.mediaPlayerController queue:nil usingBlock:^(NSNotification * _Nonnull note) {
+ XCTFail(@"No subtitle track change is expected");
+ }];
+
+ [self.mediaPlayerController playURL:SwissTracksOnDemandTestURL()];
+
+ [self waitForExpectationsWithTimeout:30. handler:^(NSError * _Nullable error) {
+ [NSNotificationCenter.defaultCenter removeObserver:subtitleTrackObserver];
+ }];
+
+ XCTAssertNil([self selectedLanguageCodeInMediaSelectionGroupWithCharacteristic:AVMediaCharacteristicLegible]);
+}
+
+- (void)testAlwaysOnBehaviorWithoutForcedSubtitles
+{
+ MACaptionAppearanceSetDisplayType(kMACaptionAppearanceDomainUser, kMACaptionAppearanceDisplayTypeAlwaysOn);
+ MACaptionAppearanceAddSelectedLanguage(kMACaptionAppearanceDomainUser, (__bridge CFStringRef _Nonnull)@"fr");
+
+ [self expectationForSingleNotification:SRGMediaPlayerSubtitleTrackDidChangeNotification object:self.mediaPlayerController handler:^BOOL(NSNotification * _Nonnull notification) {
+ XCTAssertNil(notification.userInfo[SRGMediaPlayerPreviousTrackKey]);
+
+ AVMediaSelectionOption *subtitleOption = notification.userInfo[SRGMediaPlayerTrackKey];
+ XCTAssertEqualObjects([subtitleOption.locale objectForKey:NSLocaleLanguageCode], @"fr");
+ XCTAssertTrue([subtitleOption hasMediaCharacteristic:AVMediaCharacteristicLegible]);
+ XCTAssertFalse([subtitleOption hasMediaCharacteristic:AVMediaCharacteristicContainsOnlyForcedSubtitles]);
+ return YES;
+ }];
+
+ [self.mediaPlayerController playURL:SwissTracksOnDemandTestURL()];
+
+ [self waitForExpectationsWithTimeout:30. handler:nil];
+
+ XCTAssertEqualObjects([self selectedLanguageCodeInMediaSelectionGroupWithCharacteristic:AVMediaCharacteristicLegible], @"fr");
+}
+
- (void)testSubtitleStyleCustomization
{
MACaptionAppearanceSetDisplayType(kMACaptionAppearanceDomainUser, kMACaptionAppearanceDisplayTypeAutomatic);
@@ -227,8 +351,13 @@ - (void)testMediaConfigurationReloadDuringPlayback
[self waitForExpectationsWithTimeout:30. handler:nil];
[self expectationForSingleNotification:SRGMediaPlayerSubtitleTrackDidChangeNotification object:self.mediaPlayerController handler:^BOOL(NSNotification * _Nonnull notification) {
- XCTAssertNil([[notification.userInfo[SRGMediaPlayerPreviousTrackKey] locale] objectForKey:NSLocaleLanguageCode]);
- XCTAssertEqualObjects([[notification.userInfo[SRGMediaPlayerTrackKey] locale] objectForKey:NSLocaleLanguageCode], @"ja");
+ AVMediaSelectionOption *previousSubtitleOption = notification.userInfo[SRGMediaPlayerPreviousTrackKey];
+ XCTAssertEqualObjects([previousSubtitleOption.locale objectForKey:NSLocaleLanguageCode], @"en");
+ XCTAssertTrue([previousSubtitleOption hasMediaCharacteristic:AVMediaCharacteristicContainsOnlyForcedSubtitles]);
+
+ AVMediaSelectionOption *subtitleOption = notification.userInfo[SRGMediaPlayerTrackKey];
+ XCTAssertEqualObjects([subtitleOption.locale objectForKey:NSLocaleLanguageCode], @"ja");
+ XCTAssertFalse([subtitleOption hasMediaCharacteristic:AVMediaCharacteristicContainsOnlyForcedSubtitles]);
return YES;
}];
diff --git a/fastlane/Fastfile b/fastlane/Fastfile
index d828f1b3..9ac69b4d 100644
--- a/fastlane/Fastfile
+++ b/fastlane/Fastfile
@@ -91,7 +91,7 @@ def srg_xcodebuild(device, test_build)
end
def srg_test_xcargs
- '-retry-tests-on-failure'
+ '-retry-tests-on-failure -testLanguage en -testRegion en-US'
end
def srg_xcodebuild_workspace(test_build)