From 744f050f8f9011748adf3cc6bb1f7aa4794ab639 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pawel=20Lozi=C5=84ski?= Date: Wed, 12 Jun 2024 12:23:06 +0200 Subject: [PATCH 01/37] from: Fix for tvOS native audio menu language selector commit: a42240d --- ios/Video/Features/RCTPlayerOperations.swift | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/ios/Video/Features/RCTPlayerOperations.swift b/ios/Video/Features/RCTPlayerOperations.swift index 9c80c85f3e..2bf97a27b1 100644 --- a/ios/Video/Features/RCTPlayerOperations.swift +++ b/ios/Video/Features/RCTPlayerOperations.swift @@ -116,12 +116,20 @@ enum RCTPlayerOperations { } } } else { // default. invalid type or "system" + #if os(tvOS) + // Do noting. Fix for tvOS native audio menu language selector + #else await player?.currentItem?.selectMediaOptionAutomatically(in: group) + #endif return } - + // If a match isn't found, option will be nil and text tracks will be disabled + #if os(tvOS) + // Do noting. Fix for tvOS native audio menu language selector + #else // If a match isn't found, option will be nil and text tracks will be disabled await player?.currentItem?.select(mediaOption, in: group) + #endif } static func seek(player: AVPlayer, playerItem: AVPlayerItem, paused: Bool, seekTime: Float, seekTolerance: Float, completion: @escaping (Bool) -> Void) { From f1e7ec6a43a5fdc1dfa07284298ef317392fa1e7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pawel=20Lozi=C5=84ski?= Date: Wed, 12 Jun 2024 12:39:19 +0200 Subject: [PATCH 02/37] from: Adding Dolby Atmos workaround. commit: 3030ddd comment: - Add and remove spatial audio remote command handler - Whenever we play video, we must add this event handler that returns a success status for all remote command events - Whenever we stop video or destroy the player, remove this event handler - This is a workaround to get Dolby Atmos working on iOS 15 and above --- ios/Video/Features/RCTPlayerOperations.swift | 28 ++++++++++++++++++++ ios/Video/RCTVideo.swift | 2 ++ 2 files changed, 30 insertions(+) diff --git a/ios/Video/Features/RCTPlayerOperations.swift b/ios/Video/Features/RCTPlayerOperations.swift index 2bf97a27b1..445dce9bec 100644 --- a/ios/Video/Features/RCTPlayerOperations.swift +++ b/ios/Video/Features/RCTPlayerOperations.swift @@ -1,5 +1,6 @@ import AVFoundation import MediaAccessibility +import MediaPlayer let RCTVideoUnset = -1 @@ -9,6 +10,9 @@ let RCTVideoUnset = -1 * Collection of mutating functions */ enum RCTPlayerOperations { + + static var remoteCommandHandlerForSpatialAudio: Any? + static func setSideloadedText(player: AVPlayer?, textTracks: [TextTrack], criteria: SelectedTrackCriteria?) { let type = criteria?.type @@ -204,4 +208,28 @@ enum RCTPlayerOperations { } } } + + // MARK: - Spatial Audio / Dolby Atmos Workaround + + /* These functions are a temporarily workaround to enable the rendering of Dolby Atmos on + * iOS 15 and above. + */ + + static func addSpatialAudioRemoteCommandHandler() { + let command = MPRemoteCommandCenter.shared().playCommand + + remoteCommandHandlerForSpatialAudio = command.addTarget(handler: { event in + MPRemoteCommandHandlerStatus.success + }) + } + + static func removeSpatialAudioRemoteCommandHandler() { + + let command = MPRemoteCommandCenter.shared().playCommand + + if let remoteCommand = RCTPlayerOperations.remoteCommandHandlerForSpatialAudio { + command.removeTarget(remoteCommand) + RCTPlayerOperations.remoteCommandHandlerForSpatialAudio = nil + } + } } diff --git a/ios/Video/RCTVideo.swift b/ios/Video/RCTVideo.swift index 486524d947..35f39cbe16 100644 --- a/ios/Video/RCTVideo.swift +++ b/ios/Video/RCTVideo.swift @@ -718,9 +718,11 @@ class RCTVideo: UIView, RCTVideoPlayerViewControllerDelegate, RCTPlayerObserverH } else { _player?.pause() _player?.rate = 0.0 + RCTPlayerOperations.addSpatialAudioRemoteCommandHandler() } } else { RCTPlayerOperations.configureAudio(ignoreSilentSwitch: _ignoreSilentSwitch, mixWithOthers: _mixWithOthers, audioOutput: _audioOutput) + RCTPlayerOperations.removeSpatialAudioRemoteCommandHandler() if _adPlaying { #if USE_GOOGLE_IMA From 51033e83eb827741439f67f63d1c88929e9ab0c6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pawel=20Lozi=C5=84ski?= Date: Wed, 12 Jun 2024 12:55:32 +0200 Subject: [PATCH 03/37] from: It seems, that if we have several Video elements and if we remove handler, spatial sound will stop working. commit: b1fa382 comment: How to reproduce: Two videos. First with autoplay. Second paused with controls. Also handler adding moved from play/pause method to setSrc, because if we show paused video with native controls - our play method is not called. --- ios/Video/RCTVideo.swift | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ios/Video/RCTVideo.swift b/ios/Video/RCTVideo.swift index 35f39cbe16..7d4b442c05 100644 --- a/ios/Video/RCTVideo.swift +++ b/ios/Video/RCTVideo.swift @@ -516,6 +516,7 @@ class RCTVideo: UIView, RCTVideoPlayerViewControllerDelegate, RCTPlayerObserverH self._playerObserver.player = nil self._resouceLoaderDelegate = nil self._playerObserver.playerItem = nil + RCTPlayerOperations.removeSpatialAudioRemoteCommandHandler() // perform on next run loop, otherwise other passed react-props may not be set RCTVideoUtils.delay { [weak self] in @@ -524,6 +525,7 @@ class RCTVideo: UIView, RCTVideoPlayerViewControllerDelegate, RCTPlayerObserverH let playerItem = try await self.preparePlayerItem() try await setupPlayer(playerItem: playerItem) + RCTPlayerOperations.addSpatialAudioRemoteCommandHandler() } catch { DebugLog("An error occurred: \(error.localizedDescription)") @@ -718,11 +720,9 @@ class RCTVideo: UIView, RCTVideoPlayerViewControllerDelegate, RCTPlayerObserverH } else { _player?.pause() _player?.rate = 0.0 - RCTPlayerOperations.addSpatialAudioRemoteCommandHandler() } } else { RCTPlayerOperations.configureAudio(ignoreSilentSwitch: _ignoreSilentSwitch, mixWithOthers: _mixWithOthers, audioOutput: _audioOutput) - RCTPlayerOperations.removeSpatialAudioRemoteCommandHandler() if _adPlaying { #if USE_GOOGLE_IMA From 84e5c58fe727ed922f90d053f156b59d256bf6b3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pawel=20Lozi=C5=84ski?= Date: Wed, 12 Jun 2024 17:35:27 +0200 Subject: [PATCH 04/37] from: Revert iOS 15 MPRemoteCommandCenter workaround commit: ac1114e --- ios/Video/Features/RCTPlayerOperations.swift | 26 -------------------- ios/Video/RCTVideo.swift | 2 -- 2 files changed, 28 deletions(-) diff --git a/ios/Video/Features/RCTPlayerOperations.swift b/ios/Video/Features/RCTPlayerOperations.swift index 445dce9bec..19fe0b46cd 100644 --- a/ios/Video/Features/RCTPlayerOperations.swift +++ b/ios/Video/Features/RCTPlayerOperations.swift @@ -11,8 +11,6 @@ let RCTVideoUnset = -1 */ enum RCTPlayerOperations { - static var remoteCommandHandlerForSpatialAudio: Any? - static func setSideloadedText(player: AVPlayer?, textTracks: [TextTrack], criteria: SelectedTrackCriteria?) { let type = criteria?.type @@ -208,28 +206,4 @@ enum RCTPlayerOperations { } } } - - // MARK: - Spatial Audio / Dolby Atmos Workaround - - /* These functions are a temporarily workaround to enable the rendering of Dolby Atmos on - * iOS 15 and above. - */ - - static func addSpatialAudioRemoteCommandHandler() { - let command = MPRemoteCommandCenter.shared().playCommand - - remoteCommandHandlerForSpatialAudio = command.addTarget(handler: { event in - MPRemoteCommandHandlerStatus.success - }) - } - - static func removeSpatialAudioRemoteCommandHandler() { - - let command = MPRemoteCommandCenter.shared().playCommand - - if let remoteCommand = RCTPlayerOperations.remoteCommandHandlerForSpatialAudio { - command.removeTarget(remoteCommand) - RCTPlayerOperations.remoteCommandHandlerForSpatialAudio = nil - } - } } diff --git a/ios/Video/RCTVideo.swift b/ios/Video/RCTVideo.swift index 7d4b442c05..486524d947 100644 --- a/ios/Video/RCTVideo.swift +++ b/ios/Video/RCTVideo.swift @@ -516,7 +516,6 @@ class RCTVideo: UIView, RCTVideoPlayerViewControllerDelegate, RCTPlayerObserverH self._playerObserver.player = nil self._resouceLoaderDelegate = nil self._playerObserver.playerItem = nil - RCTPlayerOperations.removeSpatialAudioRemoteCommandHandler() // perform on next run loop, otherwise other passed react-props may not be set RCTVideoUtils.delay { [weak self] in @@ -525,7 +524,6 @@ class RCTVideo: UIView, RCTVideoPlayerViewControllerDelegate, RCTPlayerObserverH let playerItem = try await self.preparePlayerItem() try await setupPlayer(playerItem: playerItem) - RCTPlayerOperations.addSpatialAudioRemoteCommandHandler() } catch { DebugLog("An error occurred: \(error.localizedDescription)") From 23dab1f610396d4eed87b4bedf4b683b4e9014c2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pawel=20Lozi=C5=84ski?= Date: Wed, 12 Jun 2024 17:42:25 +0200 Subject: [PATCH 05/37] from: feat: put back dolby atmos workaround work commit: c58d9dd --- ios/Video/Features/RCTPlayerOperations.swift | 26 ++++++++++++++++++++ ios/Video/RCTVideo.swift | 3 +++ 2 files changed, 29 insertions(+) diff --git a/ios/Video/Features/RCTPlayerOperations.swift b/ios/Video/Features/RCTPlayerOperations.swift index 19fe0b46cd..445dce9bec 100644 --- a/ios/Video/Features/RCTPlayerOperations.swift +++ b/ios/Video/Features/RCTPlayerOperations.swift @@ -11,6 +11,8 @@ let RCTVideoUnset = -1 */ enum RCTPlayerOperations { + static var remoteCommandHandlerForSpatialAudio: Any? + static func setSideloadedText(player: AVPlayer?, textTracks: [TextTrack], criteria: SelectedTrackCriteria?) { let type = criteria?.type @@ -206,4 +208,28 @@ enum RCTPlayerOperations { } } } + + // MARK: - Spatial Audio / Dolby Atmos Workaround + + /* These functions are a temporarily workaround to enable the rendering of Dolby Atmos on + * iOS 15 and above. + */ + + static func addSpatialAudioRemoteCommandHandler() { + let command = MPRemoteCommandCenter.shared().playCommand + + remoteCommandHandlerForSpatialAudio = command.addTarget(handler: { event in + MPRemoteCommandHandlerStatus.success + }) + } + + static func removeSpatialAudioRemoteCommandHandler() { + + let command = MPRemoteCommandCenter.shared().playCommand + + if let remoteCommand = RCTPlayerOperations.remoteCommandHandlerForSpatialAudio { + command.removeTarget(remoteCommand) + RCTPlayerOperations.remoteCommandHandlerForSpatialAudio = nil + } + } } diff --git a/ios/Video/RCTVideo.swift b/ios/Video/RCTVideo.swift index 486524d947..cb7bca9c62 100644 --- a/ios/Video/RCTVideo.swift +++ b/ios/Video/RCTVideo.swift @@ -268,6 +268,7 @@ class RCTVideo: UIView, RCTVideoPlayerViewControllerDelegate, RCTPlayerObserverH _player?.pause() _player?.rate = 0.0 + RCTPlayerOperations.removeSpatialAudioRemoteCommandHandler() } @objc @@ -524,6 +525,7 @@ class RCTVideo: UIView, RCTVideoPlayerViewControllerDelegate, RCTPlayerObserverH let playerItem = try await self.preparePlayerItem() try await setupPlayer(playerItem: playerItem) + RCTPlayerOperations.addSpatialAudioRemoteCommandHandler() } catch { DebugLog("An error occurred: \(error.localizedDescription)") @@ -1123,6 +1125,7 @@ class RCTVideo: UIView, RCTVideoPlayerViewControllerDelegate, RCTPlayerObserverH _playerLayer?.removeFromSuperlayer() _playerLayer = nil _playerObserver.playerLayer = nil + RCTPlayerOperations.removeSpatialAudioRemoteCommandHandler() } @objc From 3a903b207c62342e98c99c6e30b2af42c705f4e3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pawel=20Lozi=C5=84ski?= Date: Wed, 12 Jun 2024 18:42:28 +0200 Subject: [PATCH 06/37] from: Send current time request and handle command result on iOS. commit: dbba295 --- ios/Video/RCTVideo.swift | 18 ++++++++++++++++++ ios/Video/RCTVideoManager.m | 6 ++++++ ios/Video/RCTVideoManager.swift | 7 +++++++ 3 files changed, 31 insertions(+) diff --git a/ios/Video/RCTVideo.swift b/ios/Video/RCTVideo.swift index cb7bca9c62..a22d0e62c5 100644 --- a/ios/Video/RCTVideo.swift +++ b/ios/Video/RCTVideo.swift @@ -133,6 +133,7 @@ class RCTVideo: UIView, RCTVideoPlayerViewControllerDelegate, RCTPlayerObserverH @objc var onPictureInPictureStatusChanged: RCTDirectEventBlock? @objc var onRestoreUserInterfaceForPictureInPictureStop: RCTDirectEventBlock? @objc var onGetLicense: RCTDirectEventBlock? + @objc var onCommandResult: RCTDirectEventBlock? @objc var onReceiveAdEvent: RCTDirectEventBlock? @objc var onTextTracks: RCTDirectEventBlock? @objc var onAudioTracks: RCTDirectEventBlock? @@ -1292,6 +1293,23 @@ class RCTVideo: UIView, RCTVideoPlayerViewControllerDelegate, RCTPlayerObserverH func setLicenseResultError(_ error: String!, _ licenseUrl: String!) { _resouceLoaderDelegate?.setLicenseResultError(error, licenseUrl) } + + func requestedCurrentTime(_ requestId: NSNumber!) { + if let onCommandResult { + return + } + + let result: [NSString: NSNumber] = [ + "requestId": requestId, + "result": getCurrentTime() + ] + onCommandResult?(result) + } + + private func getCurrentTime() -> NSNumber { + let time = _playerItem != nil ? CMTimeGetSeconds(_playerItem?.currentTime() ?? .zero) : 0 + return NSNumber(value: time) + } func dismissFullscreenPlayer() { setFullscreen(false) diff --git a/ios/Video/RCTVideoManager.m b/ios/Video/RCTVideoManager.m index d0ead34a9c..5c4500ee09 100644 --- a/ios/Video/RCTVideoManager.m +++ b/ios/Video/RCTVideoManager.m @@ -63,6 +63,12 @@ @interface RCT_EXTERN_MODULE (RCTVideoManager, RCTViewManager) RCT_EXPORT_VIEW_PROPERTY(onGetLicense, RCTDirectEventBlock); RCT_EXPORT_VIEW_PROPERTY(onPictureInPictureStatusChanged, RCTDirectEventBlock); RCT_EXPORT_VIEW_PROPERTY(onRestoreUserInterfaceForPictureInPictureStop, RCTDirectEventBlock); + +RCT_EXPORT_VIEW_PROPERTY(onCommandResult, RCTBubblingEventBlock); +RCT_EXTERN_METHOD(getCurrentTime + : (nonnull NSNumber*)rectTag reactTag + : (nonnull NSNumber *)commandId commandId) + RCT_EXPORT_VIEW_PROPERTY(onReceiveAdEvent, RCTDirectEventBlock); RCT_EXPORT_VIEW_PROPERTY(onTextTracks, RCTDirectEventBlock); RCT_EXPORT_VIEW_PROPERTY(onAudioTracks, RCTDirectEventBlock); diff --git a/ios/Video/RCTVideoManager.swift b/ios/Video/RCTVideoManager.swift index d8053f37ea..3d4595b960 100644 --- a/ios/Video/RCTVideoManager.swift +++ b/ios/Video/RCTVideoManager.swift @@ -62,6 +62,13 @@ class RCTVideoManager: RCTViewManager { videoView?.dismissFullscreenPlayer() }) } + + @objc(getCurrentTime:commandId:) + func getCurrentTime(_ reactTag: NSNumber, commandId: NSNumber) { + performOnVideoView(withReactTag: reactTag, callback: { videoView in + videoView?.requestedCurrentTime(commandId) + }) + } @objc(presentFullscreenPlayer:) func presentFullscreenPlayer(_ reactTag: NSNumber) { From 5e3132e0b2ae8e3a9b8e8976b62515bfe9b61ec5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pawel=20Lozi=C5=84ski?= Date: Wed, 12 Jun 2024 23:24:17 +0200 Subject: [PATCH 07/37] from: Attempt to sync two videos. commit: 300794d --- ios/RCTVideo.xcodeproj/project.pbxproj | 8 + ios/Video/CurrentVideos.h | 19 + ios/Video/CurrentVideos.m | 55 ++ ios/Video/RCTVideo-Bridging-Header.h | 1 + ios/Video/RCTVideo.swift | 1003 ++++++++++++++---------- ios/Video/RCTVideoManager.m | 2 + ios/Video/WeakVideoRef.h | 17 + ios/Video/WeakVideoRef.m | 12 + src/Video.tsx | 12 + 9 files changed, 733 insertions(+), 396 deletions(-) create mode 100644 ios/Video/CurrentVideos.h create mode 100644 ios/Video/CurrentVideos.m create mode 100644 ios/Video/WeakVideoRef.h create mode 100644 ios/Video/WeakVideoRef.m diff --git a/ios/RCTVideo.xcodeproj/project.pbxproj b/ios/RCTVideo.xcodeproj/project.pbxproj index 1665197d03..353ad91b49 100644 --- a/ios/RCTVideo.xcodeproj/project.pbxproj +++ b/ios/RCTVideo.xcodeproj/project.pbxproj @@ -49,6 +49,10 @@ 0177D39827170A7A00F5BE18 /* RCTVideo-Bridging-Header.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = "RCTVideo-Bridging-Header.h"; path = "Video/RCTVideo-Bridging-Header.h"; sourceTree = ""; }; 0177D39927170A7A00F5BE18 /* RCTVideo.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = RCTVideo.swift; path = Video/RCTVideo.swift; sourceTree = ""; }; 134814201AA4EA6300B7C361 /* libRCTVideo.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = libRCTVideo.a; sourceTree = BUILT_PRODUCTS_DIR; }; + DCF8FEFD2C1A174E00CCA6B0 /* CurrentVideos.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = CurrentVideos.h; path = Video/CurrentVideos.h; sourceTree = ""; }; + DCF8FEFE2C1A174E00CCA6B0 /* CurrentVideos.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; name = CurrentVideos.m; path = Video/CurrentVideos.m; sourceTree = ""; }; + DCF8FEFF2C1A18AC00CCA6B0 /* WeakVideoRef.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = WeakVideoRef.h; path = Video/WeakVideoRef.h; sourceTree = ""; }; + DCF8FF002C1A18AC00CCA6B0 /* WeakVideoRef.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; name = WeakVideoRef.m; path = Video/WeakVideoRef.m; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -91,6 +95,10 @@ 01489051272001A100E69940 /* Features */, 0177D39527170A7A00F5BE18 /* RCTSwiftLog */, 0177D39927170A7A00F5BE18 /* RCTVideo.swift */, + DCF8FEFD2C1A174E00CCA6B0 /* CurrentVideos.h */, + DCF8FEFE2C1A174E00CCA6B0 /* CurrentVideos.m */, + DCF8FEFF2C1A18AC00CCA6B0 /* WeakVideoRef.h */, + DCF8FF002C1A18AC00CCA6B0 /* WeakVideoRef.m */, 0177D39727170A7A00F5BE18 /* RCTVideoManager.m */, 0177D39227170A7A00F5BE18 /* RCTVideoManager.swift */, 0177D39427170A7A00F5BE18 /* RCTVideoPlayerViewController.swift */, diff --git a/ios/Video/CurrentVideos.h b/ios/Video/CurrentVideos.h new file mode 100644 index 0000000000..769b2bf8e4 --- /dev/null +++ b/ios/Video/CurrentVideos.h @@ -0,0 +1,19 @@ +// +// CurrentVideos.h +// react-native-video +// +// Created by marcin.dziennik on 9/8/23. +// + +#import + +NS_ASSUME_NONNULL_BEGIN + +@class RCTVideo; +@interface CurrentVideos : NSObject ++ (instancetype)shared; +- (void)add: (RCTVideo*)video forTag: (NSNumber*)tag; +- (nullable RCTVideo*)videoForTag: (NSNumber*)tag; +@end + +NS_ASSUME_NONNULL_END diff --git a/ios/Video/CurrentVideos.m b/ios/Video/CurrentVideos.m new file mode 100644 index 0000000000..1481e3d253 --- /dev/null +++ b/ios/Video/CurrentVideos.m @@ -0,0 +1,55 @@ +// +// CurrentVideos.m +// react-native-video +// +// Created by marcin.dziennik on 9/8/23. +// + +#import "CurrentVideos.h" +#import "WeakVideoRef.h" + +@interface CurrentVideos () +@property (strong) NSMutableDictionary *videos; +@end + +@implementation CurrentVideos + ++ (nonnull instancetype)shared { + static CurrentVideos *instance = nil; + static dispatch_once_t onceToken; + dispatch_once(&onceToken, ^{ + instance = [[self alloc] init]; + }); + return instance; +} + +- (instancetype)init { + if (self = [super init]) { + _videos = [[NSMutableDictionary alloc] init]; + } + + return self; +} + +- (void)add:(nonnull RCTVideo *)video forTag:(nonnull NSNumber *)tag { + [self cleanup]; + WeakVideoRef *ref = [[WeakVideoRef alloc] init]; + ref.video = video; + [_videos setObject:ref forKey:tag]; +} + + +- (nullable RCTVideo *)videoForTag:(nonnull NSNumber *)tag { + [self cleanup]; + return [[_videos objectForKey:tag] video]; +} + +- (void)cleanup { + for (NSNumber* key in [_videos allKeys]) { + if (_videos[key].video == nil) { + [_videos removeObjectForKey:key]; + } + } +} + +@end diff --git a/ios/Video/RCTVideo-Bridging-Header.h b/ios/Video/RCTVideo-Bridging-Header.h index 6522d5ae53..a01ef01390 100644 --- a/ios/Video/RCTVideo-Bridging-Header.h +++ b/ios/Video/RCTVideo-Bridging-Header.h @@ -1,5 +1,6 @@ #import "RCTVideoSwiftLog.h" #import +#import "CurrentVideos.h" #if __has_include() #import "RCTVideoCache.h" diff --git a/ios/Video/RCTVideo.swift b/ios/Video/RCTVideo.swift index a22d0e62c5..dc6f8d7665 100644 --- a/ios/Video/RCTVideo.swift +++ b/ios/Video/RCTVideo.swift @@ -9,37 +9,46 @@ import React // MARK: - RCTVideo class RCTVideo: UIView, RCTVideoPlayerViewControllerDelegate, RCTPlayerObserverHandler { + + struct VideoState: OptionSet { + let rawValue: UInt + + static let unknown = VideoState(rawValue: 0) + static let loaded = VideoState(rawValue: 1 << 0) + static let ready = VideoState(rawValue: 1 << 1) + } + private var _player: AVPlayer? private var _playerItem: AVPlayerItem? private var _source: VideoSource? private var _playerLayer: AVPlayerLayer? private var _chapters: [Chapter]? - + private var _playerViewController: RCTVideoPlayerViewController? private var _videoURL: NSURL? - + /* DRM */ private var _drm: DRMParams? - + private var _localSourceEncryptionKeyScheme: String? - + /* Required to publish events */ private var _eventDispatcher: RCTEventDispatcher? private var _videoLoadStarted = false - + private var _pendingSeek = false private var _pendingSeekTime: Float = 0.0 private var _lastSeekTime: Float = 0.0 - + /* For sending videoProgress events */ private var _controls = false - + /* Keep track of any modifiers, need to be applied after each play */ private var _audioOutput: String = "speaker" private var _volume: Float = 1.0 private var _rate: Float = 1.0 private var _maxBitRate: Float? - + private var _automaticallyWaitsToMinimizeStalling = true private var _muted = false private var _paused = false @@ -67,47 +76,51 @@ class RCTVideo: UIView, RCTVideoPlayerViewControllerDelegate, RCTPlayerObserverH private var _presentingViewController: UIViewController? private var _startPosition: Float64 = -1 private var _showNotificationControls = false + private var _masterVideo: NSNumber? + private var _slaveVideo: NSNumber? + private var _videoState: VideoState = .unknown + private var _masterPendingPlayRequest = false private var _pictureInPictureEnabled = false { didSet { - #if os(iOS) - if _pictureInPictureEnabled { - initPictureinPicture() - _playerViewController?.allowsPictureInPicturePlayback = true - } else { - _pip?.deinitPipController() - _playerViewController?.allowsPictureInPicturePlayback = false - } - #endif +#if os(iOS) + if _pictureInPictureEnabled { + initPictureinPicture() + _playerViewController?.allowsPictureInPicturePlayback = true + } else { + _pip?.deinitPipController() + _playerViewController?.allowsPictureInPicturePlayback = false + } +#endif } } - + private var _isBuffering = false { didSet { onVideoBuffer?(["isBuffering": _isBuffering, "target": reactTag as Any]) } } - + /* IMA Ads */ private var _adTagUrl: String? - #if USE_GOOGLE_IMA - private var _imaAdsManager: RCTIMAAdsManager! - /* Playhead used by the SDK to track content video progress and insert mid-rolls. */ - private var _contentPlayhead: IMAAVPlayerContentPlayhead? - #endif +#if USE_GOOGLE_IMA + private var _imaAdsManager: RCTIMAAdsManager! + /* Playhead used by the SDK to track content video progress and insert mid-rolls. */ + private var _contentPlayhead: IMAAVPlayerContentPlayhead? +#endif private var _didRequestAds = false private var _adPlaying = false - + private var _resouceLoaderDelegate: RCTResourceLoaderDelegate? private var _playerObserver: RCTPlayerObserver = .init() - - #if USE_VIDEO_CACHING - private let _videoCache: RCTVideoCachingHandler = .init() - #endif - - #if os(iOS) - private var _pip: RCTPictureInPicture? - #endif - + +#if USE_VIDEO_CACHING + private let _videoCache: RCTVideoCachingHandler = .init() +#endif + +#if os(iOS) + private var _pip: RCTPictureInPicture? +#endif + // Events @objc var onVideoLoadStart: RCTDirectEventBlock? @objc var onVideoLoad: RCTDirectEventBlock? @@ -138,96 +151,96 @@ class RCTVideo: UIView, RCTVideoPlayerViewControllerDelegate, RCTPlayerObserverH @objc var onTextTracks: RCTDirectEventBlock? @objc var onAudioTracks: RCTDirectEventBlock? @objc var onTextTrackDataChanged: RCTDirectEventBlock? - + @objc func _onPictureInPictureEnter() { onPictureInPictureStatusChanged?(["isActive": NSNumber(value: true)]) } - + @objc func _onPictureInPictureExit() { onPictureInPictureStatusChanged?(["isActive": NSNumber(value: false)]) } - + func handlePictureInPictureEnter() { onPictureInPictureStatusChanged?(["isActive": NSNumber(value: true)]) } - + func handlePictureInPictureExit() { onPictureInPictureStatusChanged?(["isActive": NSNumber(value: false)]) } - + func handleRestoreUserInterfaceForPictureInPictureStop() { onRestoreUserInterfaceForPictureInPictureStop?([:]) } - + func isPipEnabled() -> Bool { return _pictureInPictureEnabled } - + func initPictureinPicture() { - #if os(iOS) - _pip = RCTPictureInPicture({ [weak self] in - self?._onPictureInPictureEnter() - }, { [weak self] in - self?._onPictureInPictureExit() - }, { [weak self] in - self?.onRestoreUserInterfaceForPictureInPictureStop?([:]) - }) - - if _playerLayer != nil && !_controls { - _pip?.setupPipController(_playerLayer) - } - #else - DebugLog("Picture in Picture is not supported on this platform") - #endif +#if os(iOS) + _pip = RCTPictureInPicture({ [weak self] in + self?._onPictureInPictureEnter() + }, { [weak self] in + self?._onPictureInPictureExit() + }, { [weak self] in + self?.onRestoreUserInterfaceForPictureInPictureStop?([:]) + }) + + if _playerLayer != nil && !_controls { + _pip?.setupPipController(_playerLayer) + } +#else + DebugLog("Picture in Picture is not supported on this platform") +#endif } - + init(eventDispatcher: RCTEventDispatcher!) { super.init(frame: CGRect(x: 0, y: 0, width: 100, height: 100)) - #if USE_GOOGLE_IMA - _imaAdsManager = RCTIMAAdsManager(video: self, pipEnabled: isPipEnabled) - #endif - +#if USE_GOOGLE_IMA + _imaAdsManager = RCTIMAAdsManager(video: self, pipEnabled: isPipEnabled) +#endif + _eventDispatcher = eventDispatcher - - #if os(iOS) - if _pictureInPictureEnabled { - initPictureinPicture() - _playerViewController?.allowsPictureInPicturePlayback = true - } else { - _playerViewController?.allowsPictureInPicturePlayback = false - } - #endif - + +#if os(iOS) + if _pictureInPictureEnabled { + initPictureinPicture() + _playerViewController?.allowsPictureInPicturePlayback = true + } else { + _playerViewController?.allowsPictureInPicturePlayback = false + } +#endif + NotificationCenter.default.addObserver( self, selector: #selector(applicationWillResignActive(notification:)), name: UIApplication.willResignActiveNotification, object: nil ) - + NotificationCenter.default.addObserver( self, selector: #selector(applicationDidBecomeActive(notification:)), name: UIApplication.didBecomeActiveNotification, object: nil ) - + NotificationCenter.default.addObserver( self, selector: #selector(applicationDidEnterBackground(notification:)), name: UIApplication.didEnterBackgroundNotification, object: nil ) - + NotificationCenter.default.addObserver( self, selector: #selector(applicationWillEnterForeground(notification:)), name: UIApplication.willEnterForegroundNotification, object: nil ) - + NotificationCenter.default.addObserver( self, selector: #selector(audioRouteChanged(notification:)), @@ -235,52 +248,52 @@ class RCTVideo: UIView, RCTVideoPlayerViewControllerDelegate, RCTPlayerObserverH object: nil ) _playerObserver._handlers = self - #if USE_VIDEO_CACHING - _videoCache.playerItemPrepareText = playerItemPrepareText - #endif +#if USE_VIDEO_CACHING + _videoCache.playerItemPrepareText = playerItemPrepareText +#endif } - + required init?(coder aDecoder: NSCoder) { super.init(coder: aDecoder) - #if USE_GOOGLE_IMA - _imaAdsManager = RCTIMAAdsManager(video: self, pipEnabled: isPipEnabled) - #endif +#if USE_GOOGLE_IMA + _imaAdsManager = RCTIMAAdsManager(video: self, pipEnabled: isPipEnabled) +#endif } - + deinit { NotificationCenter.default.removeObserver(self) self.removePlayerLayer() _playerObserver.clearPlayer() - + if let player = _player { NowPlayingInfoCenterManager.shared.removePlayer(player: player) } - - #if os(iOS) - _pip = nil - #endif + +#if os(iOS) + _pip = nil +#endif } - + // MARK: - App lifecycle handlers - + @objc func applicationWillResignActive(notification _: NSNotification!) { if _playInBackground || _playWhenInactive || _paused { return } - + _player?.pause() _player?.rate = 0.0 RCTPlayerOperations.removeSpatialAudioRemoteCommandHandler() } - + @objc func applicationDidBecomeActive(notification _: NSNotification!) { if _playInBackground || _playWhenInactive || _paused { return } - + // Resume the player or any other tasks that should continue when the app becomes active. _player?.play() _player?.rate = _rate } - + @objc func applicationDidEnterBackground(notification _: NSNotification!) { if !_playInBackground { @@ -289,7 +302,7 @@ class RCTVideo: UIView, RCTVideoPlayerViewControllerDelegate, RCTPlayerObserverH _playerViewController?.player = nil } } - + @objc func applicationWillEnterForeground(notification _: NSNotification!) { self.applyModifiers() @@ -298,9 +311,9 @@ class RCTVideo: UIView, RCTVideoPlayerViewControllerDelegate, RCTPlayerObserverH _playerViewController?.player = _player } } - + // MARK: - Audio events - + @objc func audioRouteChanged(notification: NSNotification!) { if let userInfo = notification.userInfo { @@ -311,25 +324,25 @@ class RCTVideo: UIView, RCTVideoPlayerViewControllerDelegate, RCTPlayerObserverH } } } - + // MARK: - Progress - + func sendProgressUpdate() { - #if !USE_GOOGLE_IMA - // If we dont use Ads and onVideoProgress is not defined we dont need to run this code - guard onVideoProgress != nil else { return } - #endif - +#if !USE_GOOGLE_IMA + // If we dont use Ads and onVideoProgress is not defined we dont need to run this code + guard onVideoProgress != nil else { return } +#endif + if let video = _player?.currentItem, video == nil || video.status != AVPlayerItem.Status.readyToPlay { return } - + let playerDuration: CMTime = RCTVideoUtils.playerItemDuration(_player) if CMTIME_IS_INVALID(playerDuration) { return } - + var currentTime = _player?.currentTime() if currentTime != nil && _source?.cropStart != nil { currentTime = CMTimeSubtract(currentTime!, CMTimeMake(value: _source?.cropStart ?? 0, timescale: 1000)) @@ -337,18 +350,18 @@ class RCTVideo: UIView, RCTVideoPlayerViewControllerDelegate, RCTPlayerObserverH let currentPlaybackTime = _player?.currentItem?.currentDate() let duration = CMTimeGetSeconds(playerDuration) let currentTimeSecs = CMTimeGetSeconds(currentTime ?? .zero) - + NotificationCenter.default.post(name: NSNotification.Name("RCTVideo_progress"), object: nil, userInfo: [ "progress": NSNumber(value: currentTimeSecs / duration), ]) - + if currentTimeSecs >= 0 { - #if USE_GOOGLE_IMA - if !_didRequestAds && currentTimeSecs >= 0.0001 && _adTagUrl != nil { - _imaAdsManager.requestAds() - _didRequestAds = true - } - #endif +#if USE_GOOGLE_IMA + if !_didRequestAds && currentTimeSecs >= 0.0001 && _adTagUrl != nil { + _imaAdsManager.requestAds() + _didRequestAds = true + } +#endif onVideoProgress?([ "currentTime": NSNumber(value: Float(currentTimeSecs)), "playableDuration": RCTVideoUtils.calculatePlayableDuration(_player, withSource: _source), @@ -359,10 +372,10 @@ class RCTVideo: UIView, RCTVideoPlayerViewControllerDelegate, RCTPlayerObserverH ]) } } - + var isSetSourceOngoing = false var nextSource: NSDictionary? - + func applyNextSource() { if self.nextSource != nil { DebugLog("apply next source") @@ -372,9 +385,9 @@ class RCTVideo: UIView, RCTVideoPlayerViewControllerDelegate, RCTPlayerObserverH self.setSrc(nextSrc) } } - + // MARK: - Player and source - + func preparePlayerItem() async throws -> AVPlayerItem { guard let source = _source else { DebugLog("The source not exist") @@ -382,7 +395,7 @@ class RCTVideo: UIView, RCTVideoPlayerViewControllerDelegate, RCTPlayerObserverH applyNextSource() throw NSError(domain: "", code: 0, userInfo: nil) } - + // Perform on next run loop, otherwise onVideoLoadStart is nil onVideoLoadStart?([ "src": [ @@ -393,12 +406,12 @@ class RCTVideo: UIView, RCTVideoPlayerViewControllerDelegate, RCTPlayerObserverH "drm": _drm?.json ?? NSNull(), "target": reactTag, ]) - + if let uri = source.uri, uri.starts(with: "ph://") { let photoAsset = await RCTVideoUtils.preparePHAsset(uri: uri) return await playerItemPrepareText(asset: photoAsset, assetOptions: nil, uri: source.uri ?? "") } - + guard let assetResult = RCTVideoUtils.prepareAsset(source: source), let asset = assetResult.asset, let assetOptions = assetResult.assetOptions else { @@ -407,7 +420,7 @@ class RCTVideo: UIView, RCTVideoPlayerViewControllerDelegate, RCTPlayerObserverH applyNextSource() throw NSError(domain: "", code: 0, userInfo: nil) } - + guard let assetResult = RCTVideoUtils.prepareAsset(source: source), let asset = assetResult.asset, let assetOptions = assetResult.assetOptions else { @@ -416,17 +429,17 @@ class RCTVideo: UIView, RCTVideoPlayerViewControllerDelegate, RCTPlayerObserverH applyNextSource() throw NSError(domain: "", code: 0, userInfo: nil) } - + if let startPosition = _source?.startPosition { _startPosition = startPosition / 1000 } - - #if USE_VIDEO_CACHING - if _videoCache.shouldCache(source: source, textTracks: _textTracks) { - return try await _videoCache.playerItemForSourceUsingCache(uri: source.uri, assetOptions: assetOptions) - } - #endif - + +#if USE_VIDEO_CACHING + if _videoCache.shouldCache(source: source, textTracks: _textTracks) { + return try await _videoCache.playerItemForSourceUsingCache(uri: source.uri, assetOptions: assetOptions) + } +#endif + if _drm != nil || _localSourceEncryptionKeyScheme != nil { _resouceLoaderDelegate = RCTResourceLoaderDelegate( asset: asset, @@ -437,16 +450,16 @@ class RCTVideo: UIView, RCTVideoPlayerViewControllerDelegate, RCTPlayerObserverH reactTag: reactTag ) } - + return await playerItemPrepareText(asset: asset, assetOptions: assetOptions, uri: source.uri ?? "") } - + func setupPlayer(playerItem: AVPlayerItem) async throws { if !isSetSourceOngoing { DebugLog("setSrc has been canceled last step") return } - + _player?.pause() _playerItem = playerItem _playerObserver.playerItem = _playerItem @@ -456,40 +469,40 @@ class RCTVideo: UIView, RCTVideoPlayerViewControllerDelegate, RCTPlayerObserverH if let maxBitRate = _maxBitRate { _playerItem?.preferredPeakBitRate = Double(maxBitRate) } - + if _player == nil { _player = AVPlayer() _player!.replaceCurrentItem(with: playerItem) - + // We need to register player after we set current item and only for init NowPlayingInfoCenterManager.shared.registerPlayer(player: _player!) } else { _player?.replaceCurrentItem(with: playerItem) - + // later we can just call "updateMetadata: NowPlayingInfoCenterManager.shared.updateMetadata() } - + _playerObserver.player = _player applyModifiers() _player?.actionAtItemEnd = .none - + if #available(iOS 10.0, *) { setAutomaticallyWaitsToMinimizeStalling(_automaticallyWaitsToMinimizeStalling) } - - #if USE_GOOGLE_IMA - if _adTagUrl != nil { - // Set up your content playhead and contentComplete callback. - _contentPlayhead = IMAAVPlayerContentPlayhead(avPlayer: _player!) - - _imaAdsManager.setUpAdsLoader() - } - #endif + +#if USE_GOOGLE_IMA + if _adTagUrl != nil { + // Set up your content playhead and contentComplete callback. + _contentPlayhead = IMAAVPlayerContentPlayhead(avPlayer: _player!) + + _imaAdsManager.setUpAdsLoader() + } +#endif isSetSourceOngoing = false applyNextSource() } - + @objc func setSrc(_ source: NSDictionary!) { if self.isSetSourceOngoing || self.nextSource != nil { @@ -499,18 +512,18 @@ class RCTVideo: UIView, RCTVideoPlayerViewControllerDelegate, RCTPlayerObserverH return } self.isSetSourceOngoing = true - + let initializeSource = { self._source = VideoSource(source) if self._source?.uri == nil || self._source?.uri == "" { self._player?.replaceCurrentItem(with: nil) self.isSetSourceOngoing = false self.applyNextSource() - + if let player = self._player { NowPlayingInfoCenterManager.shared.removePlayer(player: player) } - + DebugLog("setSrc Stopping playback") return } @@ -518,52 +531,53 @@ class RCTVideo: UIView, RCTVideoPlayerViewControllerDelegate, RCTPlayerObserverH self._playerObserver.player = nil self._resouceLoaderDelegate = nil self._playerObserver.playerItem = nil - + // perform on next run loop, otherwise other passed react-props may not be set RCTVideoUtils.delay { [weak self] in do { guard let self else { throw NSError(domain: "", code: 0, userInfo: nil) } - + let playerItem = try await self.preparePlayerItem() try await setupPlayer(playerItem: playerItem) RCTPlayerOperations.addSpatialAudioRemoteCommandHandler() } catch { DebugLog("An error occurred: \(error.localizedDescription)") - + if let self { self.onVideoError?(["error": error.localizedDescription]) self.isSetSourceOngoing = false self.applyNextSource() - + if let player = self._player { NowPlayingInfoCenterManager.shared.removePlayer(player: player) } } } } - + self._videoLoadStarted = true + CurrentVideos.shared().add(self, forTag: self.reactTag) self.applyNextSource() } - + DispatchQueue.global(qos: .default).async(execute: initializeSource) } - + @objc func setDrm(_ drm: NSDictionary) { _drm = DRMParams(drm) } - + @objc func setLocalSourceEncryptionKeyScheme(_ keyScheme: String) { _localSourceEncryptionKeyScheme = keyScheme } - + func playerItemPrepareText(asset: AVAsset!, assetOptions: NSDictionary?, uri: String) async -> AVPlayerItem { if (self._textTracks == nil) || self._textTracks?.isEmpty == true || (uri.hasSuffix(".m3u8")) { return await self.playerItemPropegateMetadata(AVPlayerItem(asset: asset)) } - + // AVPlayer can't airplay AVMutableCompositions self._allowsExternalPlayback = false let mixComposition = await RCTVideoUtils.generateMixComposition(asset) @@ -573,57 +587,57 @@ class RCTVideo: UIView, RCTVideoPlayerViewControllerDelegate, RCTPlayerObserverH mixComposition: mixComposition, textTracks: self._textTracks ) - + if validTextTracks.count != self._textTracks?.count { self.setTextTracks(validTextTracks) } - + return await self.playerItemPropegateMetadata(AVPlayerItem(asset: mixComposition)) } - + func playerItemPropegateMetadata(_ playerItem: AVPlayerItem!) async -> AVPlayerItem { var mapping: [AVMetadataIdentifier: Any] = [:] - + if let title = _source?.customMetadata?.title { mapping[.commonIdentifierTitle] = title } - + if let artist = _source?.customMetadata?.artist { mapping[.commonIdentifierArtist] = artist } - + if let subtitle = _source?.customMetadata?.subtitle { mapping[.iTunesMetadataTrackSubTitle] = subtitle } - + if let description = _source?.customMetadata?.description { mapping[.commonIdentifierDescription] = description } - + if let imageUri = _source?.customMetadata?.imageUri, let imageData = await RCTVideoUtils.createImageMetadataItem(imageUri: imageUri) { mapping[.commonIdentifierArtwork] = imageData } - + if #available(iOS 12.2, *), !mapping.isEmpty { playerItem.externalMetadata = RCTVideoUtils.createMetadataItems(for: mapping) } - - #if os(tvOS) - if let chapters = _chapters { - playerItem.navigationMarkerGroups = RCTVideoTVUtils.makeNavigationMarkerGroups(chapters) - } - #endif - + +#if os(tvOS) + if let chapters = _chapters { + playerItem.navigationMarkerGroups = RCTVideoTVUtils.makeNavigationMarkerGroups(chapters) + } +#endif + return playerItem } - + // MARK: - Prop setters - + @objc func setResizeMode(_ mode: String) { var resizeMode: AVLayerVideoGravity = .resizeAspect - + switch mode { case "contain": resizeMode = .resizeAspect @@ -636,99 +650,103 @@ class RCTVideo: UIView, RCTVideoPlayerViewControllerDelegate, RCTPlayerObserverH default: resizeMode = .resizeAspect } - + if _controls { _playerViewController?.videoGravity = resizeMode } else { _playerLayer?.videoGravity = resizeMode } - + _resizeMode = mode } - + @objc func setPlayInBackground(_ playInBackground: Bool) { _playInBackground = playInBackground } - + @objc func setPreventsDisplaySleepDuringVideoPlayback(_ preventsDisplaySleepDuringVideoPlayback: Bool) { _preventsDisplaySleepDuringVideoPlayback = preventsDisplaySleepDuringVideoPlayback self.applyModifiers() } - + @objc func setAllowsExternalPlayback(_ allowsExternalPlayback: Bool) { _allowsExternalPlayback = allowsExternalPlayback - #if !os(visionOS) - _player?.allowsExternalPlayback = _allowsExternalPlayback - #endif +#if !os(visionOS) + _player?.allowsExternalPlayback = _allowsExternalPlayback +#endif } - + @objc func setPlayWhenInactive(_ playWhenInactive: Bool) { _playWhenInactive = playWhenInactive } - + @objc func setPictureInPicture(_ pictureInPicture: Bool) { - #if os(iOS) - let audioSession = AVAudioSession.sharedInstance() - do { - try audioSession.setCategory(.playback) - try audioSession.setActive(true, options: []) - } catch {} - if pictureInPicture { - _pictureInPictureEnabled = true - } else { - _pictureInPictureEnabled = false - } - _pip?.setPictureInPicture(pictureInPicture) - #endif +#if os(iOS) + let audioSession = AVAudioSession.sharedInstance() + do { + try audioSession.setCategory(.playback) + try audioSession.setActive(true, options: []) + } catch {} + if pictureInPicture { + _pictureInPictureEnabled = true + } else { + _pictureInPictureEnabled = false + } + _pip?.setPictureInPicture(pictureInPicture) +#endif } - + @objc func setRestoreUserInterfaceForPIPStopCompletionHandler(_ restore: Bool) { - #if os(iOS) - if _pip != nil { - _pip?.setRestoreUserInterfaceForPIPStopCompletionHandler(restore) - } else { - _playerObserver.setRestoreUserInterfaceForPIPStopCompletionHandler(restore) - } - #endif +#if os(iOS) + if _pip != nil { + _pip?.setRestoreUserInterfaceForPIPStopCompletionHandler(restore) + } else { + _playerObserver.setRestoreUserInterfaceForPIPStopCompletionHandler(restore) + } +#endif } - + @objc func setIgnoreSilentSwitch(_ ignoreSilentSwitch: String?) { _ignoreSilentSwitch = ignoreSilentSwitch RCTPlayerOperations.configureAudio(ignoreSilentSwitch: _ignoreSilentSwitch, mixWithOthers: _mixWithOthers, audioOutput: _audioOutput) applyModifiers() } - + @objc func setMixWithOthers(_ mixWithOthers: String?) { _mixWithOthers = mixWithOthers applyModifiers() } - + @objc func setPaused(_ paused: Bool) { + if self.isManaged() { + self.setManagedPaused(paused: paused) + return + } if paused { if _adPlaying { - #if USE_GOOGLE_IMA - _imaAdsManager.getAdsManager()?.pause() - #endif +#if USE_GOOGLE_IMA + _imaAdsManager.getAdsManager()?.pause() +#endif } else { _player?.pause() _player?.rate = 0.0 } } else { RCTPlayerOperations.configureAudio(ignoreSilentSwitch: _ignoreSilentSwitch, mixWithOthers: _mixWithOthers, audioOutput: _audioOutput) - + if _adPlaying { - #if USE_GOOGLE_IMA - _imaAdsManager.getAdsManager()?.resume() - #endif +#if USE_GOOGLE_IMA + _imaAdsManager.getAdsManager()?.resume() +#endif } else { if #available(iOS 10.0, *), !_automaticallyWaitsToMinimizeStalling { _player?.playImmediately(atRate: _rate) @@ -739,12 +757,21 @@ class RCTVideo: UIView, RCTVideoPlayerViewControllerDelegate, RCTPlayerObserverH _player?.rate = _rate } } - + _paused = paused } - + @objc func setSeek(_ info: NSDictionary!) { + if self.isManaged() { + if self.isSlave() { + return + } + let slave = self.slave() + self.setSeekManaged(info: info) + slave?.setSeekManaged(info: info) + return + } let seekTime: NSNumber! = info["time"] as! NSNumber let seekTolerance: NSNumber! = info["tolerance"] as! NSNumber let item: AVPlayerItem? = _player?.currentItem @@ -753,7 +780,7 @@ class RCTVideo: UIView, RCTVideoPlayerViewControllerDelegate, RCTPlayerObserverH _pendingSeekTime = seekTime.floatValue return } - + RCTPlayerOperations.seek( player: player, playerItem: item, @@ -762,17 +789,17 @@ class RCTVideo: UIView, RCTVideoPlayerViewControllerDelegate, RCTPlayerObserverH seekTolerance: seekTolerance.floatValue ) { [weak self] (_: Bool) in guard let self else { return } - + self._playerObserver.addTimeObserverIfNotSet() self.setPaused(_paused) self.onVideoSeek?(["currentTime": NSNumber(value: Float(CMTimeGetSeconds(item.currentTime()))), "seekTime": seekTime, "target": self.reactTag]) } - + _pendingSeek = false } - + @objc func setRate(_ rate: Float) { if _rate != 1 { @@ -790,27 +817,27 @@ class RCTVideo: UIView, RCTVideoPlayerViewControllerDelegate, RCTPlayerObserverH self.applyModifiers() } } - + @objc func isMuted() -> Bool { return _muted } - + @objc func setMuted(_ muted: Bool) { _muted = muted applyModifiers() } - + @objc func setAudioOutput(_ audioOutput: String) { _audioOutput = audioOutput RCTPlayerOperations.configureAudio(ignoreSilentSwitch: _ignoreSilentSwitch, mixWithOthers: _mixWithOthers, audioOutput: _audioOutput) do { if audioOutput == "speaker" { - #if os(iOS) || os(visionOS) - try AVAudioSession.sharedInstance().overrideOutputAudioPort(AVAudioSession.PortOverride.speaker) - #endif +#if os(iOS) || os(visionOS) + try AVAudioSession.sharedInstance().overrideOutputAudioPort(AVAudioSession.PortOverride.speaker) +#endif } else if audioOutput == "earpiece" { try AVAudioSession.sharedInstance().overrideOutputAudioPort(AVAudioSession.PortOverride.none) } @@ -818,19 +845,19 @@ class RCTVideo: UIView, RCTVideoPlayerViewControllerDelegate, RCTPlayerObserverH print("Error occurred: \(error.localizedDescription)") } } - + @objc func setVolume(_ volume: Float) { _volume = volume applyModifiers() } - + @objc func setMaxBitRate(_ maxBitRate: Float) { _maxBitRate = maxBitRate _playerItem?.preferredPeakBitRate = Double(maxBitRate) } - + @objc func setPreferredForwardBufferDuration(_ preferredForwardBufferDuration: Float) { _preferredForwardBufferDuration = preferredForwardBufferDuration @@ -840,7 +867,7 @@ class RCTVideo: UIView, RCTVideoPlayerViewControllerDelegate, RCTPlayerObserverH // Fallback on earlier versions } } - + @objc func setAutomaticallyWaitsToMinimizeStalling(_ waits: Bool) { _automaticallyWaitsToMinimizeStalling = waits @@ -850,7 +877,7 @@ class RCTVideo: UIView, RCTVideoPlayerViewControllerDelegate, RCTPlayerObserverH // Fallback on earlier versions } } - + func setPlaybackRange(_ item: AVPlayerItem!, withCropStart cropStart: Int64?, withCropEnd cropEnd: Int64?) { if let cropStart { let start = CMTimeMake(value: cropStart, timescale: 1000) @@ -862,7 +889,7 @@ class RCTVideo: UIView, RCTVideoPlayerViewControllerDelegate, RCTPlayerObserverH item.forwardPlaybackEndTime = CMTimeMake(value: cropEnd, timescale: 1000) } } - + func applyModifiers() { if let video = _player?.currentItem, video == nil || video.status != AVPlayerItem.Status.readyToPlay { @@ -877,19 +904,19 @@ class RCTVideo: UIView, RCTVideoPlayerViewControllerDelegate, RCTPlayerObserverH _player?.volume = _volume _player?.isMuted = false } - + if #available(iOS 12.0, tvOS 12.0, *) { - #if !os(visionOS) - _player?.preventsDisplaySleepDuringVideoPlayback = _preventsDisplaySleepDuringVideoPlayback - #endif +#if !os(visionOS) + _player?.preventsDisplaySleepDuringVideoPlayback = _preventsDisplaySleepDuringVideoPlayback +#endif } else { // Fallback on earlier versions } - + if let _maxBitRate { setMaxBitRate(_maxBitRate) } - + setAudioOutput(_audioOutput) setSelectedAudioTrack(_selectedAudioTrackCriteria) setSelectedTextTrack(_selectedTextTrackCriteria) @@ -899,17 +926,17 @@ class RCTVideo: UIView, RCTVideoPlayerViewControllerDelegate, RCTPlayerObserverH setPaused(_paused) setAllowsExternalPlayback(_allowsExternalPlayback) } - + @objc func setRepeat(_ repeat: Bool) { _repeat = `repeat` } - + @objc func setSelectedAudioTrack(_ selectedAudioTrack: NSDictionary?) { setSelectedAudioTrack(SelectedTrackCriteria(selectedAudioTrack)) } - + func setSelectedAudioTrack(_ selectedAudioTrack: SelectedTrackCriteria?) { _selectedAudioTrackCriteria = selectedAudioTrack Task { @@ -917,12 +944,12 @@ class RCTVideo: UIView, RCTVideoPlayerViewControllerDelegate, RCTPlayerObserverH criteria: _selectedAudioTrackCriteria) } } - + @objc func setSelectedTextTrack(_ selectedTextTrack: NSDictionary?) { setSelectedTextTrack(SelectedTrackCriteria(selectedTextTrack)) } - + func setSelectedTextTrack(_ selectedTextTrack: SelectedTrackCriteria?) { _selectedTextTrackCriteria = selectedTextTrack if _textTracks != nil { // sideloaded text tracks @@ -934,28 +961,28 @@ class RCTVideo: UIView, RCTVideoPlayerViewControllerDelegate, RCTPlayerObserverH } } } - + @objc func setTextTracks(_ textTracks: [NSDictionary]?) { setTextTracks(textTracks?.map { TextTrack($0) }) } - + func setTextTracks(_ textTracks: [TextTrack]?) { _textTracks = textTracks - + // in case textTracks was set after selectedTextTrack if _selectedTextTrackCriteria != nil { setSelectedTextTrack(_selectedTextTrackCriteria) } } - + @objc func setChapters(_ chapters: [NSDictionary]?) { setChapters(chapters?.map { Chapter($0) }) } - + func setChapters(_ chapters: [Chapter]?) { _chapters = chapters } - + @objc func setFullscreen(_ fullscreen: Bool) { if fullscreen && !_fullscreenPlayerPresented && _player != nil { @@ -964,15 +991,15 @@ class RCTVideo: UIView, RCTVideoPlayerViewControllerDelegate, RCTPlayerObserverH if _playerViewController == nil { self.usePlayerViewController() } - + // Set presentation style to fullscreen _playerViewController?.modalPresentationStyle = .fullScreen - + // Find the nearest view controller var viewController: UIViewController! = self.firstAvailableUIViewController() if viewController == nil { guard let keyWindow = RCTVideoUtils.getCurrentWindow() else { return } - + viewController = keyWindow.rootViewController if !viewController.children.isEmpty { viewController = viewController.children.last @@ -980,22 +1007,22 @@ class RCTVideo: UIView, RCTVideoPlayerViewControllerDelegate, RCTPlayerObserverH } if viewController != nil { _presentingViewController = viewController - + self.onVideoFullscreenPlayerWillPresent?(["target": reactTag as Any]) - + if let playerViewController = _playerViewController { if _controls { // prevents crash https://github.com/TheWidlarzGroup/react-native-video/issues/3040 self._playerViewController?.removeFromParent() } - + viewController.present(playerViewController, animated: true, completion: { [weak self] in guard let self else { return } // In fullscreen we must display controls self._playerViewController?.showsPlaybackControls = true self._fullscreenPlayerPresented = fullscreen self._playerViewController?.autorotate = self._fullscreenAutorotate - + self.onVideoFullscreenPlayerDidPresent?(["target": self.reactTag]) }) } @@ -1007,7 +1034,7 @@ class RCTVideo: UIView, RCTVideoPlayerViewControllerDelegate, RCTPlayerObserverH }) } } - + @objc func setFullscreenAutorotate(_ autorotate: Bool) { _fullscreenAutorotate = autorotate @@ -1015,7 +1042,7 @@ class RCTVideo: UIView, RCTVideoPlayerViewControllerDelegate, RCTPlayerObserverH _playerViewController?.autorotate = autorotate } } - + @objc func setFullscreenOrientation(_ orientation: String?) { _fullscreenOrientation = orientation @@ -1023,37 +1050,37 @@ class RCTVideo: UIView, RCTVideoPlayerViewControllerDelegate, RCTPlayerObserverH _playerViewController?.preferredOrientation = orientation } } - + func usePlayerViewController() { guard let _player, let _playerItem else { return } - + if _playerViewController == nil { _playerViewController = createPlayerViewController(player: _player, withPlayerItem: _playerItem) } // to prevent video from being animated when resizeMode is 'cover' // resize mode must be set before subview is added setResizeMode(_resizeMode) - + guard let _playerViewController else { return } - + if _controls { let viewController: UIViewController! = self.reactViewController() viewController?.addChild(_playerViewController) self.addSubview(_playerViewController.view) } - + _playerObserver.playerViewController = _playerViewController } - + func createPlayerViewController(player: AVPlayer, withPlayerItem _: AVPlayerItem) -> RCTVideoPlayerViewController { let viewController = RCTVideoPlayerViewController() viewController.showsPlaybackControls = self._controls - #if !os(tvOS) - viewController.updatesNowPlayingInfoCenter = false - #endif +#if !os(tvOS) + viewController.updatesNowPlayingInfoCenter = false +#endif viewController.rctDelegate = self viewController.preferredOrientation = _fullscreenOrientation - + viewController.view.frame = self.bounds viewController.player = player if #available(tvOS 14.0, *) { @@ -1061,30 +1088,30 @@ class RCTVideo: UIView, RCTVideoPlayerViewControllerDelegate, RCTPlayerObserverH } return viewController } - + func usePlayerLayer() { if let _player { _playerLayer = AVPlayerLayer(player: _player) _playerLayer?.frame = self.bounds _playerLayer?.needsDisplayOnBoundsChange = true - + // to prevent video from being animated when resizeMode is 'cover' // resize mode must be set before layer is added setResizeMode(_resizeMode) _playerObserver.playerLayer = _playerLayer - + if let _playerLayer { self.layer.addSublayer(_playerLayer) } self.layer.needsDisplayOnBoundsChange = true - #if os(iOS) - if _pictureInPictureEnabled { - _pip?.setupPipController(_playerLayer) - } - #endif +#if os(iOS) + if _pictureInPictureEnabled { + _pip?.setupPipController(_playerLayer) + } +#endif } } - + @objc func setControls(_ controls: Bool) { if _controls != controls || ((_playerLayer == nil) && (_playerViewController == nil)) { @@ -1101,51 +1128,51 @@ class RCTVideo: UIView, RCTVideoPlayerViewControllerDelegate, RCTPlayerObserverH } } } - + @objc func setShowNotificationControls(_ showNotificationControls: Bool) { guard let player = _player else { return } - + if showNotificationControls { NowPlayingInfoCenterManager.shared.registerPlayer(player: player) } else { NowPlayingInfoCenterManager.shared.removePlayer(player: player) } - + _showNotificationControls = showNotificationControls } - + @objc func setProgressUpdateInterval(_ progressUpdateInterval: Float) { _playerObserver.replaceTimeObserverIfSet(Float64(progressUpdateInterval)) } - + func removePlayerLayer() { _playerLayer?.removeFromSuperlayer() _playerLayer = nil _playerObserver.playerLayer = nil RCTPlayerOperations.removeSpatialAudioRemoteCommandHandler() } - + @objc func setSubtitleStyle(_ style: [String: Any]) { let subtitleStyle = SubtitleStyle.parse(from: style) _playerObserver.subtitleStyle = subtitleStyle } - + // MARK: - RCTVideoPlayerViewControllerDelegate - + func videoPlayerViewControllerWillDismiss(playerViewController: AVPlayerViewController) { if _playerViewController == playerViewController && _fullscreenPlayerPresented, - let onVideoFullscreenPlayerWillDismiss { + let onVideoFullscreenPlayerWillDismiss { _playerObserver.removePlayerViewControllerObservers() onVideoFullscreenPlayerWillDismiss(["target": reactTag as Any]) } } - + func videoPlayerViewControllerDidDismiss(playerViewController: AVPlayerViewController) { if _playerViewController == playerViewController && _fullscreenPlayerPresented { _fullscreenPlayerPresented = false @@ -1153,15 +1180,15 @@ class RCTVideo: UIView, RCTVideoPlayerViewControllerDelegate, RCTPlayerObserverH _playerViewController = nil _playerObserver.playerViewController = nil self.applyModifiers() - + onVideoFullscreenPlayerDidDismiss?(["target": reactTag as Any]) } } - + @objc func setFilter(_ filterName: String!) { _filterName = filterName - + if !_filterEnabled { return } else if let uri = _source?.uri, uri.contains("m3u8") { @@ -1169,41 +1196,41 @@ class RCTVideo: UIView, RCTVideoPlayerViewControllerDelegate, RCTPlayerObserverH } else if _playerItem?.asset == nil { return } - + let filter: CIFilter! = CIFilter(name: filterName) Task { let composition = await RCTVideoUtils.generateVideoComposition(asset: _playerItem!.asset, filter: filter) self._playerItem?.videoComposition = composition } } - + @objc func setFilterEnabled(_ filterEnabled: Bool) { _filterEnabled = filterEnabled } - + // MARK: - RCTIMAAdsManager - + func getAdTagUrl() -> String? { return _adTagUrl } - + @objc func setAdTagUrl(_ adTagUrl: String!) { _adTagUrl = adTagUrl } - - #if USE_GOOGLE_IMA - func getContentPlayhead() -> IMAAVPlayerContentPlayhead? { - return _contentPlayhead - } - #endif + +#if USE_GOOGLE_IMA + func getContentPlayhead() -> IMAAVPlayerContentPlayhead? { + return _contentPlayhead + } +#endif func setAdPlaying(_ adPlaying: Bool) { _adPlaying = adPlaying } - + // MARK: - React View Management - + func insertReactSubview(view: UIView!, atIndex: Int) { if _controls { view.frame = self.bounds @@ -1213,7 +1240,7 @@ class RCTVideo: UIView, RCTVideoPlayerViewControllerDelegate, RCTPlayerObserverH } return } - + func removeReactSubview(subview: UIView!) { if _controls { subview.removeFromSuperview() @@ -1222,12 +1249,12 @@ class RCTVideo: UIView, RCTVideoPlayerViewControllerDelegate, RCTPlayerObserverH } return } - + override func layoutSubviews() { super.layoutSubviews() if _controls, let _playerViewController { _playerViewController.view.frame = bounds - + // also adjust all subviews of contentOverlayView for subview in _playerViewController.contentOverlayView?.subviews ?? [] { subview.frame = bounds @@ -1239,25 +1266,25 @@ class RCTVideo: UIView, RCTVideoPlayerViewControllerDelegate, RCTPlayerObserverH CATransaction.commit() } } - + // MARK: - Lifecycle - + override func removeFromSuperview() { if let player = _player { player.pause() NowPlayingInfoCenterManager.shared.removePlayer(player: player) } - + _player = nil _resouceLoaderDelegate = nil _playerObserver.clearPlayer() - - #if USE_GOOGLE_IMA - _imaAdsManager.releaseAds() - #endif - + +#if USE_GOOGLE_IMA + _imaAdsManager.releaseAds() +#endif + self.removePlayerLayer() - + if let _playerViewController { _playerViewController.view.removeFromSuperview() _playerViewController.removeFromParent() @@ -1266,16 +1293,16 @@ class RCTVideo: UIView, RCTVideoPlayerViewControllerDelegate, RCTPlayerObserverH self._playerViewController = nil _playerObserver.playerViewController = nil } - + _eventDispatcher = nil // swiftlint:disable:next notification_center_detachment NotificationCenter.default.removeObserver(self) - + super.removeFromSuperview() } - + // MARK: - Export - + @objc func save(options: NSDictionary!, resolve: @escaping RCTPromiseResolveBlock, reject: @escaping RCTPromiseRejectBlock) { RCTVideoSave.save( @@ -1285,11 +1312,11 @@ class RCTVideo: UIView, RCTVideoPlayerViewControllerDelegate, RCTPlayerObserverH playerItem: _playerItem ) } - + func setLicenseResult(_ license: String!, _ licenseUrl: String!) { _resouceLoaderDelegate?.setLicenseResult(license, licenseUrl) } - + func setLicenseResultError(_ error: String!, _ licenseUrl: String!) { _resouceLoaderDelegate?.setLicenseResultError(error, licenseUrl) } @@ -1310,66 +1337,67 @@ class RCTVideo: UIView, RCTVideoPlayerViewControllerDelegate, RCTPlayerObserverH let time = _playerItem != nil ? CMTimeGetSeconds(_playerItem?.currentTime() ?? .zero) : 0 return NSNumber(value: time) } - + func dismissFullscreenPlayer() { setFullscreen(false) } - + func presentFullscreenPlayer() { setFullscreen(true) } - + // MARK: - RCTPlayerObserverHandler - + func handleTimeUpdate(time _: CMTime) { sendProgressUpdate() } - + func handleReadyForDisplay(changeObject _: Any, change _: NSKeyValueObservedChange) { if _isBuffering { _isBuffering = false } + self.update(state: .ready) onReadyForDisplay?([ "target": reactTag, ]) } - + // When timeMetadata is read the event onTimedMetadata is triggered func handleTimeMetadataChange(timedMetadata: [AVMetadataItem]) { guard onTimedMetadata != nil else { return } - + var metadata: [[String: String?]?] = [] for item in timedMetadata { let value = item.value as? String let identifier = item.identifier?.rawValue - + if let value { metadata.append(["value": value, "identifier": identifier]) } } - + onTimedMetadata?([ "target": reactTag, "metadata": metadata, ]) } - + // Handle player item status change. func handlePlayerItemStatusChange(playerItem _: AVPlayerItem, change _: NSKeyValueObservedChange) { guard let _playerItem else { return } - + if _playerItem.status == .readyToPlay { handleReadyToPlay() } else if _playerItem.status == .failed { handlePlaybackFailed() } } - + func handleReadyToPlay() { guard let _playerItem else { return } - + Task { if self._pendingSeek { self.setSeek([ @@ -1378,7 +1406,7 @@ class RCTVideo: UIView, RCTVideoPlayerViewControllerDelegate, RCTPlayerObserverH ]) self._pendingSeek = false } - + if self._startPosition >= 0 { self.setSeek([ "time": NSNumber(value: self._startPosition), @@ -1386,10 +1414,11 @@ class RCTVideo: UIView, RCTVideoPlayerViewControllerDelegate, RCTPlayerObserverH ]) self._startPosition = -1 } - + if onVideoLoad != nil, self._videoLoadStarted { + self.update(state: .loaded) var duration = Float(CMTimeGetSeconds(_playerItem.asset.duration)) - + if duration.isNaN || duration == 0 { // This is a safety check for live video. // AVPlayer report a 0 duration @@ -1398,11 +1427,11 @@ class RCTVideo: UIView, RCTVideoPlayerViewControllerDelegate, RCTPlayerObserverH duration = 0 } } - + var width: Float = 0 var height: Float = 0 var orientation = "undefined" - + let tracks = await RCTVideoAssetsUtils.getTracks(asset: _playerItem.asset, withMediaType: .video) var presentationSize = _playerItem.presentationSize if presentationSize.height != 0.0 { @@ -1414,7 +1443,7 @@ class RCTVideo: UIView, RCTVideoPlayerViewControllerDelegate, RCTPlayerObserverH height = Float(naturalSize.height) } orientation = width > height ? "landscape" : width == height ? "square" : "portrait" - + let audioTracks = await RCTVideoUtils.getAudioTrackInfo(self._player) let textTracks = await RCTVideoUtils.getTextTrackInfo(self._player) self.onVideoLoad?(["duration": NSNumber(value: duration), @@ -1426,26 +1455,26 @@ class RCTVideo: UIView, RCTVideoPlayerViewControllerDelegate, RCTPlayerObserverH "canStepBackward": NSNumber(value: _playerItem.canStepBackward), "canStepForward": NSNumber(value: _playerItem.canStepForward), "naturalSize": [ - "width": width, - "height": height, - "orientation": orientation, + "width": width, + "height": height, + "orientation": orientation, ], "audioTracks": audioTracks, "textTracks": self._textTracks?.compactMap { $0.json } ?? textTracks.map(\.json), "target": self.reactTag as Any]) } - + self._videoLoadStarted = false self._playerObserver.attachPlayerEventListeners() self.applyModifiers() } } - + func handlePlaybackFailed() { if let player = _player { NowPlayingInfoCenterManager.shared.removePlayer(player: player) } - + guard let _playerItem else { return } onVideoError?( [ @@ -1453,29 +1482,43 @@ class RCTVideo: UIView, RCTVideoPlayerViewControllerDelegate, RCTPlayerObserverH "code": NSNumber(value: (_playerItem.error! as NSError).code), "localizedDescription": _playerItem.error?.localizedDescription == nil ? "" : _playerItem.error?.localizedDescription, "localizedFailureReason": ((_playerItem.error! as NSError).localizedFailureReason == nil ? - "" : (_playerItem.error! as NSError).localizedFailureReason) ?? "", + "" : (_playerItem.error! as NSError).localizedFailureReason) ?? "", "localizedRecoverySuggestion": ((_playerItem.error! as NSError).localizedRecoverySuggestion == nil ? - "" : (_playerItem.error! as NSError).localizedRecoverySuggestion) ?? "", + "" : (_playerItem.error! as NSError).localizedRecoverySuggestion) ?? "", "domain": (_playerItem.error as! NSError).domain, ], "target": reactTag, ] ) } - + func handlePlaybackBufferKeyEmpty(playerItem _: AVPlayerItem, change _: NSKeyValueObservedChange) { if !_isBuffering { _isBuffering = true } } - + // Continue playing (or not if paused) after being paused due to hitting an unbuffered zone. func handlePlaybackLikelyToKeepUp(playerItem _: AVPlayerItem, change _: NSKeyValueObservedChange) { + if self.isManaged() { + if self.isSlave() { + self.onSlaveVideoStatusChange() + } else { + _masterPendingPlayRequest = true + } + } else { + if (!_controls || _fullscreenPlayerPresented || _isBuffering) && _playerItem?.isPlaybackLikelyToKeepUp ?? false { + self.setPaused(_paused) + } + _isBuffering = false + self.onVideoBuffer?(["isBuffering": _isBuffering, "target": self.reactTag as Any]) + } + if _isBuffering { _isBuffering = false } } - + func handleTimeControlStatusChange(player: AVPlayer, change: NSKeyValueObservedChange) { if player.timeControlStatus == change.oldValue && change.oldValue != nil { return @@ -1484,61 +1527,61 @@ class RCTVideo: UIView, RCTVideoPlayerViewControllerDelegate, RCTPlayerObserverH return } let isPlaying = player.timeControlStatus == .playing - + guard _isPlaying == nil || _isPlaying! != isPlaying else { return } _isPlaying = isPlaying onVideoPlaybackStateChanged?(["isPlaying": isPlaying, "target": reactTag as Any]) } - + func handlePlaybackRateChange(player: AVPlayer, change: NSKeyValueObservedChange) { guard let _player else { return } - + if player.rate == change.oldValue && change.oldValue != nil { return } - + onPlaybackRateChange?(["playbackRate": NSNumber(value: _player.rate), "target": reactTag as Any]) - + if _playbackStalled && _player.rate > 0 { onPlaybackResume?(["playbackRate": NSNumber(value: _player.rate), "target": reactTag as Any]) _playbackStalled = false } } - + func handleVolumeChange(player: AVPlayer, change: NSKeyValueObservedChange) { guard let _player, onVolumeChange != nil else { return } - + if player.rate == change.oldValue && change.oldValue != nil { return } - + onVolumeChange?(["volume": NSNumber(value: _player.volume), "target": reactTag as Any]) } - + func handleExternalPlaybackActiveChange(player _: AVPlayer, change _: NSKeyValueObservedChange) { - #if !os(visionOS) - guard let _player, onVideoExternalPlaybackChange != nil else { return } - onVideoExternalPlaybackChange?(["isExternalPlaybackActive": NSNumber(value: _player.isExternalPlaybackActive), - "target": reactTag as Any]) - #endif +#if !os(visionOS) + guard let _player, onVideoExternalPlaybackChange != nil else { return } + onVideoExternalPlaybackChange?(["isExternalPlaybackActive": NSNumber(value: _player.isExternalPlaybackActive), + "target": reactTag as Any]) +#endif } - + func handleViewControllerOverlayViewFrameChange(overlayView _: UIView, change: NSKeyValueObservedChange) { let oldRect = change.oldValue let newRect = change.newValue - + guard let bounds = RCTVideoUtils.getCurrentWindow()?.bounds else { return } - + if !oldRect!.equalTo(newRect!) { // https://github.com/TheWidlarzGroup/react-native-video/issues/3085#issuecomment-1557293391 if newRect!.equalTo(bounds) { RCTLog("in fullscreen") if !_fullscreenUncontrolPlayerPresented { _fullscreenUncontrolPlayerPresented = true - + self.onVideoFullscreenPlayerWillPresent?(["target": self.reactTag as Any]) self.onVideoFullscreenPlayerDidPresent?(["target": self.reactTag as Any]) } @@ -1546,21 +1589,21 @@ class RCTVideo: UIView, RCTVideoPlayerViewControllerDelegate, RCTPlayerObserverH NSLog("not fullscreen") if _fullscreenUncontrolPlayerPresented { _fullscreenUncontrolPlayerPresented = false - + self.onVideoFullscreenPlayerWillDismiss?(["target": self.reactTag as Any]) self.onVideoFullscreenPlayerDidDismiss?(["target": self.reactTag as Any]) } } - + self.reactViewController().view.frame = bounds self.reactViewController().view.setNeedsLayout() } } - + @objc func handleDidFailToFinishPlaying(notification: NSNotification!) { guard onVideoError != nil else { return } - + let error: NSError! = notification.userInfo?[AVPlayerItemFailedToPlayToEndTimeErrorKey] as? NSError onVideoError?( [ @@ -1575,24 +1618,24 @@ class RCTVideo: UIView, RCTVideoPlayerViewControllerDelegate, RCTPlayerObserverH ] ) } - + @objc func handlePlaybackStalled(notification _: NSNotification!) { onPlaybackStalled?(["target": reactTag as Any]) _playbackStalled = true } - + @objc func handlePlayerItemDidReachEnd(notification: NSNotification!) { onVideoEnd?(["target": reactTag as Any]) - #if USE_GOOGLE_IMA - if notification.object as? AVPlayerItem == _player?.currentItem { - _imaAdsManager.getAdsLoader()?.contentComplete() - } - #endif +#if USE_GOOGLE_IMA + if notification.object as? AVPlayerItem == _player?.currentItem { + _imaAdsManager.getAdsLoader()?.contentComplete() + } +#endif if _repeat { let item: AVPlayerItem! = notification.object as? AVPlayerItem - + item.seek( to: _source?.cropStart != nil ? CMTime(value: _source!.cropStart!, timescale: 1000) : CMTime.zero, toleranceBefore: CMTime.zero, @@ -1606,19 +1649,19 @@ class RCTVideo: UIView, RCTVideoPlayerViewControllerDelegate, RCTPlayerObserverH _playerObserver.removePlayerTimeObserver() } } - + @objc func handleAVPlayerAccess(notification: NSNotification!) { guard onVideoBandwidthUpdate != nil else { return } - + guard let accessLog = (notification.object as? AVPlayerItem)?.accessLog() else { return } - + guard let lastEvent = accessLog.events.last else { return } onVideoBandwidthUpdate?(["bitrate": lastEvent.observedBitrate, "target": reactTag]) } - + func handleTracksChange(playerItem _: AVPlayerItem, change _: NSKeyValueObservedChange<[AVPlayerItemTrack]>) { if onTextTracks != nil { Task { @@ -1626,7 +1669,7 @@ class RCTVideo: UIView, RCTVideoPlayerViewControllerDelegate, RCTPlayerObserverH self.onTextTracks?(["textTracks": self._textTracks?.compactMap { $0.json } ?? textTracks.compactMap(\.json)]) } } - + if onAudioTracks != nil { Task { let audioTracks = await RCTVideoUtils.getAudioTrackInfo(self._player) @@ -1634,16 +1677,184 @@ class RCTVideo: UIView, RCTVideoPlayerViewControllerDelegate, RCTPlayerObserverH } } } - + func handleLegibleOutput(strings: [NSAttributedString]) { guard onTextTrackDataChanged != nil else { return } - + if let subtitles = strings.first { self.onTextTrackDataChanged?(["subtitleTracks": subtitles.string]) } } - + // Workaround for #3418 - https://github.com/TheWidlarzGroup/react-native-video/issues/3418#issuecomment-2043508862 @objc func setOnClick(_: Any) {} + + func setMasterVideo(tag: NSNumber) { + _masterVideo = tag + } + + func setSlaveVideo(tag: NSNumber) { + _slaveVideo = tag + } + + func isMaster() -> Bool { + _slaveVideo != nil + } + + func isSlave() -> Bool { + _masterVideo != nil + } + + func slave() -> RCTVideo? { + guard let video = _slaveVideo else { + return nil + } + return CurrentVideos.shared().video(forTag: video) + } + + func master() -> RCTVideo? { + guard let video = _masterVideo else { + return nil + } + return CurrentVideos.shared().video(forTag: video) + } + + func isManaged() -> Bool { + isSlave() || isMaster() + } + + func setManagedPaused(paused: Bool) { + if isSlave() { + return + } + guard let slave = slave() else { + return + } + var slavePlayer = slave._player + if paused { + _player?.pause() + slavePlayer?.pause() + _player?.rate = 0.0 + slavePlayer?.rate = 0.0 + } else { + if !isVideoReady() || slave.isVideoReady() { + _masterPendingPlayRequest = true + return + } + RCTPlayerOperations.configureAudio(ignoreSilentSwitch: _ignoreSilentSwitch, mixWithOthers: _mixWithOthers, audioOutput: _audioOutput) + + if #available(iOS 10.0, *), !_automaticallyWaitsToMinimizeStalling { + _player?.playImmediately(atRate: _rate) + slavePlayer?.playImmediately(atRate: _rate) + } else { + _player?.play() + slavePlayer?.play() + } + _player?.rate = _rate + slavePlayer?.rate = _rate + } + _paused = paused + slave.setRaw(paused: paused) + } + + func isVideoReady() -> Bool { + ((_videoState.rawValue & VideoState.loaded.rawValue) != 0) && ((_videoState.rawValue & VideoState.ready.rawValue) != 0) + } + + func setRaw(paused: Bool) { + _paused = paused + } + + func player() -> AVPlayer? { + _player + } + + func setSeekManaged(info: NSDictionary) { + if !isMaster() { + return + } + guard let slave = slave() else { + return + } + let seekTime: NSNumber? = info["time"] as? NSNumber + let seekTolerance: NSNumber? = info["tolerance"] as? NSNumber + + let timeScale: Int32 = 1000 + + let item: AVPlayerItem? = _player?.currentItem + let slavePlayer: AVPlayer? = slave.player() + let slaveItem: AVPlayerItem? = slavePlayer?.currentItem + if let item, item.status == .readyToPlay, let slaveItem, slaveItem.status == .readyToPlay { + // TODO: check loadedTimeRanges + + let cmSeekTime: CMTime = CMTimeMakeWithSeconds(Float64(seekTime?.floatValue ?? .zero), preferredTimescale: timeScale) + let current: CMTime = item.currentTime() + let tolerance: CMTime = CMTimeMake(value: Int64(seekTolerance?.floatValue ?? .zero), timescale: timeScale) + + if CMTimeCompare(current, cmSeekTime) != 0 { + _player?.pause() + slavePlayer?.pause() + + let seekGroup: DispatchGroup = .init() + seekGroup.enter() + _player?.seek(to: cmSeekTime, toleranceBefore: tolerance, toleranceAfter: tolerance, completionHandler: { finished in + seekGroup.leave() + }) + seekGroup.enter() + slavePlayer?.seek(to: cmSeekTime, toleranceBefore: tolerance, toleranceAfter: tolerance, completionHandler: { finished in + seekGroup.leave() + }) + + seekGroup.notify(queue: .main) { + self.seekCompletedFor(seekTime: seekTime ?? 0) + slave.seekCompletedFor(seekTime: seekTime ?? 0) + DispatchQueue.main.asyncAfter(deadline: .now() + 1) { + self.setManagedPaused(paused: false) + } + } + _pendingSeek = false + } else { + _pendingSeek = true + _pendingSeekTime = seekTime?.floatValue ?? .zero + } + } + } + + func seekCompletedFor(seekTime: NSNumber) { + let item: AVPlayerItem? = _player?.currentItem + _playerObserver.addTimeObserverIfNotSet() + if self.onVideoSeek != nil { + self.onVideoSeek?( + [ + "currentTime": NSNumber(value: CMTimeGetSeconds(item?.currentTime() ?? .zero)), + "seekTime": seekTime, + "target": self.reactTag + ] + ) + } + } + + func update(state: VideoState) { + if !self.isManaged() { + return + } + _videoState != state + if self.isSlave() { + let master = self.master() + master?.onSlaveVideoStatusChange() + } + } + + func onSlaveVideoStatusChange() { + let slave = self.slave() + if slave != nil { + return + } + if slave?.isVideoReady() ?? false && _masterPendingPlayRequest { + self.setPaused(false) + _masterPendingPlayRequest = false + } + } } + diff --git a/ios/Video/RCTVideoManager.m b/ios/Video/RCTVideoManager.m index 5c4500ee09..c3c968c3ca 100644 --- a/ios/Video/RCTVideoManager.m +++ b/ios/Video/RCTVideoManager.m @@ -16,6 +16,8 @@ @interface RCT_EXTERN_MODULE (RCTVideoManager, RCTViewManager) RCT_EXPORT_VIEW_PROPERTY(selectedAudioTrack, NSDictionary); RCT_EXPORT_VIEW_PROPERTY(chapters, NSArray); RCT_EXPORT_VIEW_PROPERTY(paused, BOOL); +RCT_EXPORT_VIEW_PROPERTY(masterVideo, NSNumber); +RCT_EXPORT_VIEW_PROPERTY(slaveVideo, NSNumber); RCT_EXPORT_VIEW_PROPERTY(muted, BOOL); RCT_EXPORT_VIEW_PROPERTY(controls, BOOL); RCT_EXPORT_VIEW_PROPERTY(audioOutput, NSString); diff --git a/ios/Video/WeakVideoRef.h b/ios/Video/WeakVideoRef.h new file mode 100644 index 0000000000..852b5264de --- /dev/null +++ b/ios/Video/WeakVideoRef.h @@ -0,0 +1,17 @@ +// +// WeakVideoRef.h +// react-native-video +// +// Created by marcin.dziennik on 9/8/23. +// + +#import + +NS_ASSUME_NONNULL_BEGIN + +@class RCTVideo; +@interface WeakVideoRef : NSObject +@property(weak) RCTVideo *video; +@end + +NS_ASSUME_NONNULL_END diff --git a/ios/Video/WeakVideoRef.m b/ios/Video/WeakVideoRef.m new file mode 100644 index 0000000000..0cb4506891 --- /dev/null +++ b/ios/Video/WeakVideoRef.m @@ -0,0 +1,12 @@ +// +// WeakVideoRef.m +// react-native-video +// +// Created by marcin.dziennik on 9/8/23. +// + +#import "WeakVideoRef.h" + +@implementation WeakVideoRef + +@end diff --git a/src/Video.tsx b/src/Video.tsx index e525799919..80b1e95e2f 100644 --- a/src/Video.tsx +++ b/src/Video.tsx @@ -66,6 +66,8 @@ export interface VideoRef { save: (options: object) => Promise; setVolume: (volume: number) => void; getCurrentTime: () => Promise; + setMasterVideoId: () => void; + setSlaveVideoId: () => void; } const Video = forwardRef( @@ -78,6 +80,8 @@ const Video = forwardRef( poster, fullscreen, drm, + masterVideo, + slaveVideo, textTracks, selectedVideoTrack, selectedAudioTrack, @@ -300,6 +304,14 @@ const Video = forwardRef( return VideoManager.setVolume(volume, getReactTag(nativeRef)); }, []); + const setMasterVideoId = useCallback((masterId: string) => { + nativeRef.current?.setNativeProps({masterVideo: masterId}); + }, []); + + const setSlaveVideoId = useCallback((slaveId: string) => { + nativeRef.current?.setNativeProps({slaveVideo: slaveId}); + }, []); + const onVideoLoadStart = useCallback( (e: NativeSyntheticEvent) => { hasPoster && setShowPoster(true); From 1a47e35d8357ca0f344957ce017b87cbc042f353 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pawel=20Lozi=C5=84ski?= Date: Wed, 12 Jun 2024 23:35:20 +0200 Subject: [PATCH 08/37] from: Fix autoplay & playing after buffering commit: 8a15097 --- ios/Video/RCTVideo.swift | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/ios/Video/RCTVideo.swift b/ios/Video/RCTVideo.swift index dc6f8d7665..ff57f1c93a 100644 --- a/ios/Video/RCTVideo.swift +++ b/ios/Video/RCTVideo.swift @@ -1793,6 +1793,7 @@ class RCTVideo: UIView, RCTVideoPlayerViewControllerDelegate, RCTPlayerObserverH let tolerance: CMTime = CMTimeMake(value: Int64(seekTolerance?.floatValue ?? .zero), timescale: timeScale) if CMTimeCompare(current, cmSeekTime) != 0 { + let wasPaused = _paused _player?.pause() slavePlayer?.pause() @@ -1810,7 +1811,7 @@ class RCTVideo: UIView, RCTVideoPlayerViewControllerDelegate, RCTPlayerObserverH self.seekCompletedFor(seekTime: seekTime ?? 0) slave.seekCompletedFor(seekTime: seekTime ?? 0) DispatchQueue.main.asyncAfter(deadline: .now() + 1) { - self.setManagedPaused(paused: false) + self.setManagedPaused(paused: wasPaused) } } _pendingSeek = false @@ -1854,6 +1855,9 @@ class RCTVideo: UIView, RCTVideoPlayerViewControllerDelegate, RCTPlayerObserverH if slave?.isVideoReady() ?? false && _masterPendingPlayRequest { self.setPaused(false) _masterPendingPlayRequest = false + } else if self.isVideoReady() && self.slave()?.isVideoReady() ?? false { + self.setPaused(false) + _masterPendingPlayRequest = false } } } From 1960c34ea370461fbfe10ad587dfec2b717a19e2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pawel=20Lozi=C5=84ski?= Date: Thu, 13 Jun 2024 13:04:54 +0200 Subject: [PATCH 09/37] from: Send on played tracks change commit: 69cd4f0 comment: adding files of M3U8Kit it is part of work from: Feature/xhub 5315 (#14) (commit: a6823bb) --- ios/RCTVideo.xcodeproj/project.pbxproj | 2 + ios/Video/M3U8Kit/LICENSE | 22 ++ ios/Video/M3U8Kit/Source/M3U8ExtXByteRange.h | 23 ++ ios/Video/M3U8Kit/Source/M3U8ExtXByteRange.m | 33 ++ ios/Video/M3U8Kit/Source/M3U8ExtXKey.h | 19 ++ ios/Video/M3U8Kit/Source/M3U8ExtXKey.m | 45 +++ ios/Video/M3U8Kit/Source/M3U8ExtXMedia.h | 54 +++ ios/Video/M3U8Kit/Source/M3U8ExtXMedia.m | 133 ++++++++ ios/Video/M3U8Kit/Source/M3U8ExtXMediaList.h | 30 ++ ios/Video/M3U8Kit/Source/M3U8ExtXMediaList.m | 153 +++++++++ ios/Video/M3U8Kit/Source/M3U8ExtXStreamInf.h | 67 ++++ ios/Video/M3U8Kit/Source/M3U8ExtXStreamInf.m | 148 +++++++++ .../M3U8Kit/Source/M3U8ExtXStreamInfList.h | 23 ++ .../M3U8Kit/Source/M3U8ExtXStreamInfList.m | 74 +++++ ios/Video/M3U8Kit/Source/M3U8LineReader.h | 18 + ios/Video/M3U8Kit/Source/M3U8LineReader.m | 33 ++ ios/Video/M3U8Kit/Source/M3U8MasterPlaylist.h | 38 +++ ios/Video/M3U8Kit/Source/M3U8MasterPlaylist.m | 223 +++++++++++++ ios/Video/M3U8Kit/Source/M3U8MediaPlaylist.h | 42 +++ ios/Video/M3U8Kit/Source/M3U8MediaPlaylist.m | 150 +++++++++ ios/Video/M3U8Kit/Source/M3U8Parser.h | 25 ++ ios/Video/M3U8Kit/Source/M3U8Parser.modulemap | 6 + ios/Video/M3U8Kit/Source/M3U8PlaylistModel.h | 90 +++++ ios/Video/M3U8Kit/Source/M3U8PlaylistModel.m | 297 +++++++++++++++++ ios/Video/M3U8Kit/Source/M3U8SegmentInfo.h | 45 +++ ios/Video/M3U8Kit/Source/M3U8SegmentInfo.m | 84 +++++ .../M3U8Kit/Source/M3U8SegmentInfoList.h | 19 ++ .../M3U8Kit/Source/M3U8SegmentInfoList.m | 46 +++ .../M3U8Kit/Source/M3U8TagsAndAttributes.h | 308 ++++++++++++++++++ ios/Video/M3U8Kit/Source/NSArray+m3u8.h | 28 ++ ios/Video/M3U8Kit/Source/NSArray+m3u8.m | 43 +++ ios/Video/M3U8Kit/Source/NSString+m3u8.h | 30 ++ ios/Video/M3U8Kit/Source/NSString+m3u8.m | 148 +++++++++ ios/Video/M3U8Kit/Source/NSURL+m3u8.h | 28 ++ ios/Video/M3U8Kit/Source/NSURL+m3u8.m | 46 +++ ios/Video/RCTVideo-Bridging-Header.h | 16 + ios/Video/RCTVideo.swift | 44 ++- ios/Video/RCTVideoManager.m | 1 + 38 files changed, 2633 insertions(+), 1 deletion(-) create mode 100644 ios/Video/M3U8Kit/LICENSE create mode 100644 ios/Video/M3U8Kit/Source/M3U8ExtXByteRange.h create mode 100644 ios/Video/M3U8Kit/Source/M3U8ExtXByteRange.m create mode 100644 ios/Video/M3U8Kit/Source/M3U8ExtXKey.h create mode 100644 ios/Video/M3U8Kit/Source/M3U8ExtXKey.m create mode 100644 ios/Video/M3U8Kit/Source/M3U8ExtXMedia.h create mode 100644 ios/Video/M3U8Kit/Source/M3U8ExtXMedia.m create mode 100644 ios/Video/M3U8Kit/Source/M3U8ExtXMediaList.h create mode 100644 ios/Video/M3U8Kit/Source/M3U8ExtXMediaList.m create mode 100644 ios/Video/M3U8Kit/Source/M3U8ExtXStreamInf.h create mode 100644 ios/Video/M3U8Kit/Source/M3U8ExtXStreamInf.m create mode 100644 ios/Video/M3U8Kit/Source/M3U8ExtXStreamInfList.h create mode 100644 ios/Video/M3U8Kit/Source/M3U8ExtXStreamInfList.m create mode 100644 ios/Video/M3U8Kit/Source/M3U8LineReader.h create mode 100644 ios/Video/M3U8Kit/Source/M3U8LineReader.m create mode 100644 ios/Video/M3U8Kit/Source/M3U8MasterPlaylist.h create mode 100644 ios/Video/M3U8Kit/Source/M3U8MasterPlaylist.m create mode 100644 ios/Video/M3U8Kit/Source/M3U8MediaPlaylist.h create mode 100644 ios/Video/M3U8Kit/Source/M3U8MediaPlaylist.m create mode 100644 ios/Video/M3U8Kit/Source/M3U8Parser.h create mode 100644 ios/Video/M3U8Kit/Source/M3U8Parser.modulemap create mode 100644 ios/Video/M3U8Kit/Source/M3U8PlaylistModel.h create mode 100644 ios/Video/M3U8Kit/Source/M3U8PlaylistModel.m create mode 100644 ios/Video/M3U8Kit/Source/M3U8SegmentInfo.h create mode 100644 ios/Video/M3U8Kit/Source/M3U8SegmentInfo.m create mode 100644 ios/Video/M3U8Kit/Source/M3U8SegmentInfoList.h create mode 100644 ios/Video/M3U8Kit/Source/M3U8SegmentInfoList.m create mode 100644 ios/Video/M3U8Kit/Source/M3U8TagsAndAttributes.h create mode 100644 ios/Video/M3U8Kit/Source/NSArray+m3u8.h create mode 100644 ios/Video/M3U8Kit/Source/NSArray+m3u8.m create mode 100644 ios/Video/M3U8Kit/Source/NSString+m3u8.h create mode 100644 ios/Video/M3U8Kit/Source/NSString+m3u8.m create mode 100644 ios/Video/M3U8Kit/Source/NSURL+m3u8.h create mode 100644 ios/Video/M3U8Kit/Source/NSURL+m3u8.m diff --git a/ios/RCTVideo.xcodeproj/project.pbxproj b/ios/RCTVideo.xcodeproj/project.pbxproj index 353ad91b49..6f5365ead8 100644 --- a/ios/RCTVideo.xcodeproj/project.pbxproj +++ b/ios/RCTVideo.xcodeproj/project.pbxproj @@ -53,6 +53,7 @@ DCF8FEFE2C1A174E00CCA6B0 /* CurrentVideos.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; name = CurrentVideos.m; path = Video/CurrentVideos.m; sourceTree = ""; }; DCF8FEFF2C1A18AC00CCA6B0 /* WeakVideoRef.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = WeakVideoRef.h; path = Video/WeakVideoRef.h; sourceTree = ""; }; DCF8FF002C1A18AC00CCA6B0 /* WeakVideoRef.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; name = WeakVideoRef.m; path = Video/WeakVideoRef.m; sourceTree = ""; }; + DCF8FF322C1AF64800CCA6B0 /* M3U8Kit */ = {isa = PBXFileReference; lastKnownFileType = folder; name = M3U8Kit; path = Video/M3U8Kit; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -91,6 +92,7 @@ 58B511D21A9E6C8500147676 = { isa = PBXGroup; children = ( + DCF8FF322C1AF64800CCA6B0 /* M3U8Kit */, 01489050272001A100E69940 /* DataStructures */, 01489051272001A100E69940 /* Features */, 0177D39527170A7A00F5BE18 /* RCTSwiftLog */, diff --git a/ios/Video/M3U8Kit/LICENSE b/ios/Video/M3U8Kit/LICENSE new file mode 100644 index 0000000000..580858ea09 --- /dev/null +++ b/ios/Video/M3U8Kit/LICENSE @@ -0,0 +1,22 @@ +The MIT License (MIT) + +Copyright (c) 2015 Sun Jin + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + diff --git a/ios/Video/M3U8Kit/Source/M3U8ExtXByteRange.h b/ios/Video/M3U8Kit/Source/M3U8ExtXByteRange.h new file mode 100644 index 0000000000..6535b0a168 --- /dev/null +++ b/ios/Video/M3U8Kit/Source/M3U8ExtXByteRange.h @@ -0,0 +1,23 @@ +// +// M3U8ExtXByteRange.h +// M3U8Kit +// +// Created by Frank on 2020/10/1. +// Copyright © 2020 M3U8Kit. All rights reserved. +// + +#import + +NS_ASSUME_NONNULL_BEGIN + +@interface M3U8ExtXByteRange : NSObject + +- (instancetype)initWithAtString:(NSString *)atString; +- (instancetype)initWithLength:(NSInteger)length offset:(NSInteger)offset; + +@property (nonatomic, assign, readonly) NSInteger length; +@property (nonatomic, assign, readonly) NSInteger offset; + +@end + +NS_ASSUME_NONNULL_END diff --git a/ios/Video/M3U8Kit/Source/M3U8ExtXByteRange.m b/ios/Video/M3U8Kit/Source/M3U8ExtXByteRange.m new file mode 100644 index 0000000000..5ad46124cb --- /dev/null +++ b/ios/Video/M3U8Kit/Source/M3U8ExtXByteRange.m @@ -0,0 +1,33 @@ +// +// M3U8ExtXByteRange.m +// M3U8Kit +// +// Created by Frank on 2020/10/1. +// Copyright © 2020 M3U8Kit. All rights reserved. +// + +#import "M3U8ExtXByteRange.h" + +@implementation M3U8ExtXByteRange + +- (instancetype)initWithAtString:(NSString *)atString { + NSArray *params = [atString componentsSeparatedByString:@"@"]; + NSInteger length = params.firstObject.integerValue; + NSInteger offset = 0; + if (params.count > 1) { + offset = MAX(0, params[1].integerValue); + } + + return [self initWithLength:length offset:offset]; +} + +- (instancetype)initWithLength:(NSInteger)length offset:(NSInteger)offset { + self = [super init]; + if (self) { + _length = length; + _offset = offset; + } + return self; +} + +@end diff --git a/ios/Video/M3U8Kit/Source/M3U8ExtXKey.h b/ios/Video/M3U8Kit/Source/M3U8ExtXKey.h new file mode 100644 index 0000000000..6cf67d0aa4 --- /dev/null +++ b/ios/Video/M3U8Kit/Source/M3U8ExtXKey.h @@ -0,0 +1,19 @@ +// +// M3U8ExtXKey.h +// M3U8Kit +// +// Created by Pierre Perrin on 01/02/2019. +// Copyright © 2019 M3U8Kit. All rights reserved. +// + +#import +@interface M3U8ExtXKey : NSObject + +- (instancetype)initWithDictionary:(NSDictionary *)dictionary; + +- (NSString *)method; +- (NSString *)url; +- (NSString *)keyFormat; +- (NSString *)iV; + +@end diff --git a/ios/Video/M3U8Kit/Source/M3U8ExtXKey.m b/ios/Video/M3U8Kit/Source/M3U8ExtXKey.m new file mode 100644 index 0000000000..0d3a4bf018 --- /dev/null +++ b/ios/Video/M3U8Kit/Source/M3U8ExtXKey.m @@ -0,0 +1,45 @@ +// +// M3U8ExtXKey.m +// M3U8Kit +// +// Created by Pierre Perrin on 01/02/2019. +// Copyright © 2019 M3U8Kit. All rights reserved. +// + +#import "M3U8ExtXKey.h" +#import "M3U8TagsAndAttributes.h" + +@interface M3U8ExtXKey() +@property (nonatomic, strong) NSDictionary *dictionary; +@end + +@implementation M3U8ExtXKey + +- (instancetype)initWithDictionary:(NSDictionary *)dictionary { + if (self = [super init]) { + self.dictionary = dictionary; + } + return self; +} + +- (NSString *)method { + return self.dictionary[M3U8_EXT_X_KEY_METHOD]; +} + +- (NSString *)url { + return self.dictionary[M3U8_EXT_X_KEY_URI]; +} + +- (NSString *)keyFormat { + return self.dictionary[M3U8_EXT_X_KEY_KEYFORMAT]; +} + +- (NSString *)iV { + return self.dictionary[M3U8_EXT_X_KEY_IV]; +} + +- (NSString *)description { + return self.dictionary.description; +} + +@end diff --git a/ios/Video/M3U8Kit/Source/M3U8ExtXMedia.h b/ios/Video/M3U8Kit/Source/M3U8ExtXMedia.h new file mode 100644 index 0000000000..eca3d44d6a --- /dev/null +++ b/ios/Video/M3U8Kit/Source/M3U8ExtXMedia.h @@ -0,0 +1,54 @@ +// +// M3U8ExtXMedia.h +// M3U8Kit +// +// Created by Sun Jin on 3/25/14. +// Copyright (c) 2014 Jin Sun. All rights reserved. +// + +#import + +/* + + /// EXT-X-MEDIA + + @format #EXT-X-MEDIA: , attibute-list: ATTR=,... + @example #EXT-X-MEDIA:TYPE=AUDIO,GROUP-ID="600k",LANGUAGE="eng",NAME="Audio",AUTOSELECT=YES,DEFAULT=YES,URI="/talks/769/audio/600k.m3u8?sponsor=Ripple",BANDWIDTH=614400 + +#define M3U8_EXT_X_MEDIA @"#EXT-X-MEDIA:" +// EXT-X-MEDIA attributes +#define M3U8_EXT_X_MEDIA_TYPE @"TYPE" // The value is enumerated-string; valid strings are AUDIO, VIDEO, SUBTITLES and CLOSED-CAPTIONS. +#define M3U8_EXT_X_MEDIA_URI @"URI" // The value is a quoted-string containing a URI that identifies the Playlist file. +#define M3U8_EXT_X_MEDIA_GROUP_ID @"GROUP-ID" // The value is a quoted-string identifying a mutually-exclusive group of renditions. +#define M3U8_EXT_X_MEDIA_LANGUAGE @"LANGUAGE" // The value is a quoted-string containing an RFC 5646 [RFC5646] language tag that identifies the primary language used in the rendition. +#define M3U8_EXT_X_MEDIA_ASSOC_LANGUAGE @"ASSOC-LANGUAGE" // The value is a quoted-string containing an RFC 5646 [RFC5646](http://tools.ietf.org/html/rfc5646) language tag that identifies a language that is associated with the rendition. +#define M3U8_EXT_X_MEDIA_NAME @"NAME" // The value is a quoted-string containing a human-readable description of the rendition. +#define M3U8_EXT_X_MEDIA_DEFAULT @"DEFAULT" // The value is an enumerated-string; valid strings are YES and NO. +#define M3U8_EXT_X_MEDIA_AUTOSELECT @"AUTOSELECT" // The value is an enumerated-string; valid strings are YES and NO. +#define M3U8_EXT_X_MEDIA_FORCED @"FORCED" // The value is an enumerated-string; valid strings are YES and NO. +#define M3U8_EXT_X_MEDIA_INSTREAM_ID @"INSTREAM-ID" // The value is a quoted-string that specifies a rendition within the segments in the Media Playlist. +#define M3U8_EXT_X_MEDIA_CHARACTERISTICS @"CHARACTERISTICS" // The value is a quoted-string containing one or more Uniform Type Identifiers [UTI] separated by comma (,) characters. + + */ + +@interface M3U8ExtXMedia : NSObject + +- (instancetype)initWithDictionary:(NSDictionary *)dictionary; + +- (NSString *)type; +- (NSURL *)URI; +- (NSString *)groupId; +- (NSString *)language; +- (NSString *)assocLanguage; +- (NSString *)name; +- (BOOL)isDefault; +- (BOOL)autoSelect; +- (BOOL)forced; +- (NSString *)instreamId; +- (NSString *)characteristics; +- (NSInteger)bandwidth; + +- (NSURL *)m3u8URL; // the absolute url of media playlist file +- (NSString *)m3u8PlainString; + +@end diff --git a/ios/Video/M3U8Kit/Source/M3U8ExtXMedia.m b/ios/Video/M3U8Kit/Source/M3U8ExtXMedia.m new file mode 100644 index 0000000000..824a607173 --- /dev/null +++ b/ios/Video/M3U8Kit/Source/M3U8ExtXMedia.m @@ -0,0 +1,133 @@ +// +// M3U8ExtXMedia.m +// M3U8Kit +// +// Created by Sun Jin on 3/25/14. +// Copyright (c) 2014 Jin Sun. All rights reserved. +// + +#import "M3U8ExtXMedia.h" +#import "M3U8TagsAndAttributes.h" + +@interface M3U8ExtXMedia() +@property (nonatomic, strong) NSDictionary *dictionary; +@end + +@implementation M3U8ExtXMedia + +- (instancetype)initWithDictionary:(NSDictionary *)dictionary { + if (self = [super init]) { + self.dictionary = dictionary; + } + return self; +} + +- (NSURL *)baseURL { + return self.dictionary[M3U8_BASE_URL]; +} + +- (NSURL *)URL { + return self.dictionary[M3U8_URL]; +} + +- (NSString *)type { + return self.dictionary[M3U8_EXT_X_MEDIA_TYPE]; +} + +- (NSURL *)URI { + NSString *originalUrl = self.dictionary[M3U8_EXT_X_MEDIA_URI]; + NSString *urlString = [originalUrl stringByReplacingOccurrencesOfString:@" " withString:@"%20"]; + return [NSURL URLWithString:urlString]; +} + +- (NSString *)groupId { + return self.dictionary[M3U8_EXT_X_MEDIA_GROUP_ID]; +} + +- (NSString *)language { + return [self.dictionary[M3U8_EXT_X_MEDIA_LANGUAGE] lowercaseString]; +} + +- (NSString *)assocLanguage { + return self.dictionary[M3U8_EXT_X_MEDIA_ASSOC_LANGUAGE]; +} + +- (NSString *)name { + return self.dictionary[M3U8_EXT_X_MEDIA_NAME]; +} + +- (BOOL)isDefault { + return [self.dictionary[M3U8_EXT_X_MEDIA_DEFAULT] boolValue]; +} + +- (BOOL)autoSelect { + return [self.dictionary[M3U8_EXT_X_MEDIA_AUTOSELECT] boolValue]; +} + +- (BOOL)forced { + return [self.dictionary[M3U8_EXT_X_MEDIA_FORCED] boolValue]; +} + +- (NSString *)instreamId { + return self.dictionary[M3U8_EXT_X_MEDIA_INSTREAM_ID]; +} + +- (NSString *)characteristics { + return self.dictionary[M3U8_EXT_X_MEDIA_CHARACTERISTICS]; +} + +- (NSInteger)bandwidth { + return [self.dictionary[M3U8_EXT_X_MEDIA_BANDWIDTH] integerValue]; +} + +- (NSURL *)m3u8URL { + if (self.URI.scheme) { + return self.URI; + } + + NSString *originalUrl = self.URI.absoluteString; + NSString *urlString = [originalUrl stringByReplacingOccurrencesOfString:@" " withString:@"%20"]; + return [NSURL URLWithString:urlString relativeToURL:[self baseURL]]; +} + +- (NSString *)description { + return [NSString stringWithString:self.dictionary.description]; +} + +/* + #EXT-X-MEDIA:TYPE=AUDIO,GROUP-ID="600k",LANGUAGE="eng",NAME="Audio",AUTOSELECT=YES,DEFAULT=YES,URI="main_media_7.m3u8",BANDWIDTH=614400 + */ +- (NSString *)m3u8PlainString { + NSMutableString *str = [NSMutableString string]; + [str appendString:M3U8_EXT_X_MEDIA]; + [str appendString:[NSString stringWithFormat:@"TYPE=%@", self.type]]; + [str appendString:[NSString stringWithFormat:@",GROUP-ID=\"%@\"", self.groupId]]; + [str appendString:[NSString stringWithFormat:@",LANGUAGE=\"%@\"", self.language]]; + [str appendString:[NSString stringWithFormat:@",NAME=\"%@\"", self.name]]; + [str appendString:[NSString stringWithFormat:@",AUTOSELECT=%@", self.autoSelect?@"YES":@"NO"]]; + [str appendString:[NSString stringWithFormat:@",DEFAULT=%@", self.isDefault?@"YES":@"NO"]]; + + NSString *fStr = self.dictionary[M3U8_EXT_X_MEDIA_FORCED]; + if (fStr.length > 0) { + [str appendString:[NSString stringWithFormat:@",FORCED=%@", fStr]]; + } + + [str appendString:[NSString stringWithFormat:@",URI=\"%@\"", self.URI.absoluteString]]; + + NSString *bStr = self.dictionary[M3U8_EXT_X_MEDIA_BANDWIDTH]; + if (bStr.length > 0) { + [str appendString:[NSString stringWithFormat:@",BANDWIDTH=%@", bStr]]; + } + + return str; +} + +@end + + + + + + + + diff --git a/ios/Video/M3U8Kit/Source/M3U8ExtXMediaList.h b/ios/Video/M3U8Kit/Source/M3U8ExtXMediaList.h new file mode 100644 index 0000000000..b63bcd2c78 --- /dev/null +++ b/ios/Video/M3U8Kit/Source/M3U8ExtXMediaList.h @@ -0,0 +1,30 @@ +// +// M3U8ExtXMediaList.h +// M3U8Kit +// +// Created by Sun Jin on 3/25/14. +// Copyright (c) 2014 Jin Sun. All rights reserved. +// + +#import +#import "M3U8ExtXMedia.h" + +@interface M3U8ExtXMediaList : NSObject + +@property (nonatomic, assign ,readonly) NSUInteger count; + +- (void)addExtXMedia:(M3U8ExtXMedia *)extXMedia; +- (M3U8ExtXMedia *)xMediaAtIndex:(NSUInteger)index; +- (M3U8ExtXMedia *)firstExtXMedia; +- (M3U8ExtXMedia *)lastExtXMedia; + +- (M3U8ExtXMediaList *)audioList; +- (M3U8ExtXMedia *)suitableAudio; + +- (M3U8ExtXMediaList *)videoList; +- (M3U8ExtXMedia *)suitableVideo; + +- (M3U8ExtXMediaList *)subtitleList; +- (M3U8ExtXMedia *)suitableSubtitle; + +@end diff --git a/ios/Video/M3U8Kit/Source/M3U8ExtXMediaList.m b/ios/Video/M3U8Kit/Source/M3U8ExtXMediaList.m new file mode 100644 index 0000000000..0e9f68a74f --- /dev/null +++ b/ios/Video/M3U8Kit/Source/M3U8ExtXMediaList.m @@ -0,0 +1,153 @@ +// +// M3U8ExtXMediaList.m +// M3U8Kit +// +// Created by Sun Jin on 3/25/14. +// Copyright (c) 2014 Jin Sun. All rights reserved. +// + +#import "M3U8ExtXMediaList.h" + +@interface M3U8ExtXMediaList () + +@property (nonatomic, strong) NSMutableArray *m3u8InfoList; + +@end + +@implementation M3U8ExtXMediaList + +- (id)init { + if (self = [super init]) { + self.m3u8InfoList = [NSMutableArray array]; + } + return self; +} + +- (NSUInteger)count { + return self.m3u8InfoList.count; +} + +- (void)addExtXMedia:(M3U8ExtXMedia *)extXMedia { + if (extXMedia) { + [self.m3u8InfoList addObject:extXMedia]; + } +} + +- (M3U8ExtXMedia *)xMediaAtIndex:(NSUInteger)index { + if (index >= self.count) { + return nil; + } + return [self.m3u8InfoList objectAtIndex:index]; +} + +- (M3U8ExtXMedia *)firstExtXMedia { + return self.m3u8InfoList.firstObject; +} + +- (M3U8ExtXMedia *)lastExtXMedia { + return self.m3u8InfoList.lastObject; +} + +- (M3U8ExtXMediaList *)audioList { + M3U8ExtXMediaList *audioList = [[M3U8ExtXMediaList alloc] init]; + NSArray *copy = [self.m3u8InfoList copy]; + for (M3U8ExtXMedia *media in copy) { + if ([media.type isEqualToString:@"AUDIO"]) { + [audioList addExtXMedia:media]; + } + } + return audioList; +} + +- (M3U8ExtXMedia *)suitableAudio { + NSString *lan = [NSLocale preferredLanguages].firstObject; + NSArray *copy = [self.m3u8InfoList copy]; + M3U8ExtXMedia *suitableAudio = nil; + for (M3U8ExtXMedia *media in copy) { + if ([media.type isEqualToString:@"AUDIO"]) { + if (nil == suitableAudio) { + suitableAudio = media; + } + if ([media.language isEqualToString:lan]) { + suitableAudio = media; + } + } + } + return suitableAudio; +} + +- (M3U8ExtXMediaList *)videoList { + M3U8ExtXMediaList *videoList = [[M3U8ExtXMediaList alloc] init]; + NSArray *copy = [self.m3u8InfoList copy]; + for (M3U8ExtXMedia *media in copy) { + if ([media.type isEqualToString:@"VIDEO"]) { + [videoList addExtXMedia:media]; + } + } + return videoList; +} + +- (M3U8ExtXMedia *)suitableVideo { + NSString *lan = [NSLocale preferredLanguages].firstObject; + NSArray *copy = [self.m3u8InfoList copy]; + M3U8ExtXMedia *suitableVideo = nil; + for (M3U8ExtXMedia *media in copy) { + if ([media.type isEqualToString:@"VIDEO"]) { + + if (nil == suitableVideo) { + suitableVideo = media; + } + if ([media.language isEqualToString:lan]) { + suitableVideo = media; + } + } + } + return suitableVideo; +} + +- (M3U8ExtXMediaList *)subtitleList { + M3U8ExtXMediaList *subtitleList = [[M3U8ExtXMediaList alloc] init]; + NSArray *copy = [self.m3u8InfoList copy]; + for (M3U8ExtXMedia *media in copy) { + if ([media.type isEqualToString:@"SUBTITLES"]) { + [subtitleList addExtXMedia:media]; + } + } + return subtitleList; +} + +- (M3U8ExtXMedia *)suitableSubtitle { + NSString *lan = [NSLocale preferredLanguages].firstObject; + NSArray *copy = [self.m3u8InfoList copy]; + M3U8ExtXMedia *suitableSubtitle = nil; + for (M3U8ExtXMedia *media in copy) { + if ([media.type isEqualToString:@"SUBTITLES"]) { + if (nil == suitableSubtitle) { + suitableSubtitle = media; + } + if ([media.language isEqualToString:lan]) { + suitableSubtitle = media; + } + } + } + return suitableSubtitle; +} + +- (NSString *)description { + return [NSString stringWithString:self.m3u8InfoList.description]; +} + +@end + + + + + + + + + + + + + diff --git a/ios/Video/M3U8Kit/Source/M3U8ExtXStreamInf.h b/ios/Video/M3U8Kit/Source/M3U8ExtXStreamInf.h new file mode 100644 index 0000000000..868a0b8424 --- /dev/null +++ b/ios/Video/M3U8Kit/Source/M3U8ExtXStreamInf.h @@ -0,0 +1,67 @@ +// +// M3U8ExtXStreamInf.h +// ILSLoader +// +// Created by Jin Sun on 13-4-15. +// Copyright (c) 2013年 iLegendSoft. All rights reserved. +// + +#import + +struct MediaResoulution{ + float width; + float height; +}; +typedef struct MediaResoulution MediaResoulution; + +extern MediaResoulution MediaResolutionMake(float width, float height); + +extern const MediaResoulution MediaResoulutionZero; +NSString * NSStringFromMediaResolution(MediaResoulution resolution); + +/*! + @class M3U8SegmentInfo + @abstract This is the class indicates #EXT-X-STREAM-INF: + in master playlist file. + + /// EXT-X-STREAM-INF + + @format #EXT-X-STREAM-INF: + + @example #EXT-X-STREAM-INF:AUDIO="600k",BANDWIDTH=915685,PROGRAM-ID=1,CODECS="avc1.42c01e,mp4a.40.2",RESOLUTION=640x360,SUBTITLES="subs" + /talks/769/video/600k.m3u8?sponsor=Ripple + + @note The EXT-X-STREAM-INF tag MUST NOT appear in a Media Playlist. + +#define M3U8_EXT_X_STREAM_INF @"#EXT-X-STREAM-INF:" +// EXT-X-STREAM-INF Attributes +#define M3U8_EXT_X_STREAM_INF_BANDWIDTH @"BANDWIDTH" // The value is a decimal-integer of bits per second. +#define M3U8_EXT_X_STREAM_INF_PROGRAM_ID @"PROGRAM-ID" // The value is a decimal-integer that uniquely identifies a particular presentation within the scope of the Playlist file. +#define M3U8_EXT_X_STREAM_INF_CODECS @"CODECS" // The value is a quoted-string containing a comma-separated list of formats. +#define M3U8_EXT_X_STREAM_INF_RESOLUTION @"RESOLUTION" // The value is a decimal-resolution describing the approximate encoded horizontal and vertical resolution of video within the presentation. +#define M3U8_EXT_X_STREAM_INF_AUDIO @"AUDIO" // The value is a quoted-string. +#define M3U8_EXT_X_STREAM_INF_VIDEO @"VIDEO" // The value is a quoted-string. +#define M3U8_EXT_X_STREAM_INF_SUBTITLES @"SUBTITLES" // The value is a quoted-string. +#define M3U8_EXT_X_STREAM_INF_CLOSED_CAPTIONS @"CLOSED-CAPTIONS" // The value can be either a quoted-string or an enumerated-string with the value NONE. +#define M3U8_EXT_X_STREAM_INF_URI @"URI" // The value is a enumerated-string containing a URI that identifies the Playlist file. + + */ +@interface M3U8ExtXStreamInf : NSObject + +@property (nonatomic, readonly, assign) NSInteger bandwidth; +@property (nonatomic, readonly, assign) NSInteger averageBandwidth; +@property (nonatomic, readonly, assign) NSInteger programId; // removed by draft 12 +@property (nonatomic, readonly, copy) NSArray *codecs; +@property (nonatomic, readonly) MediaResoulution resolution; +@property (nonatomic, readonly, copy) NSString *audio; +@property (nonatomic, readonly, copy) NSString *video; +@property (nonatomic, readonly, copy) NSString *subtitles; +@property (nonatomic, readonly, copy) NSString *closedCaptions; +@property (nonatomic, readonly, copy) NSURL *URI; + +- (instancetype)initWithDictionary:(NSDictionary *)dictionary; + +- (NSURL *)m3u8URL; // the absolute url + +- (NSString *)m3u8PlainString; + +@end diff --git a/ios/Video/M3U8Kit/Source/M3U8ExtXStreamInf.m b/ios/Video/M3U8Kit/Source/M3U8ExtXStreamInf.m new file mode 100644 index 0000000000..9143312754 --- /dev/null +++ b/ios/Video/M3U8Kit/Source/M3U8ExtXStreamInf.m @@ -0,0 +1,148 @@ +// +// M3U8ExtXStreamInf.m +// ILSLoader +// +// Created by Jin Sun on 13-4-15. +// Copyright (c) 2013年 iLegendSoft. All rights reserved. +// + +#import "M3U8ExtXStreamInf.h" +#import "M3U8TagsAndAttributes.h" + +const MediaResoulution MediaResoulutionZero = {0.f, 0.f}; + +NSString * NSStringFromMediaResolution(MediaResoulution resolution) { + return [NSString stringWithFormat:@"%gx%g", resolution.width, resolution.height]; +} + +MediaResoulution MediaResolutionFromString(NSString *string) { + NSArray *comps = [string componentsSeparatedByString:@"x"]; + if (comps.count == 2) { + float width = [comps[0] floatValue]; + float height = [comps[1] floatValue]; + return MediaResolutionMake(width, height); + } else { + return MediaResoulutionZero; + } +} + +MediaResoulution MediaResolutionMake(float width, float height) { + MediaResoulution resolution = {width, height}; + return resolution; +} + + +@interface M3U8ExtXStreamInf() +@property (nonatomic, strong) NSDictionary *dictionary; +@property (nonatomic) MediaResoulution resolution; +@end + +@implementation M3U8ExtXStreamInf + +- (instancetype)initWithDictionary:(NSDictionary *)dictionary { + if (self = [super init]) { + self.dictionary = dictionary; + self.resolution = MediaResoulutionZero; + } + return self; +} + +- (NSURL *)baseURL { + return self.dictionary[M3U8_BASE_URL]; +} + +- (NSURL *)URL { + return self.dictionary[M3U8_URL]; +} + +- (NSURL *)m3u8URL { + if (self.URI.scheme) { + return self.URI; + } + + return [NSURL URLWithString:self.URI.absoluteString relativeToURL:[self baseURL]]; +} + +- (NSInteger)bandwidth { + return [self.dictionary[M3U8_EXT_X_STREAM_INF_BANDWIDTH] integerValue]; +} + +- (NSInteger)averageBandwidth { + return [self.dictionary[M3U8_EXT_X_STREAM_INF_AVERAGE_BANDWIDTH] integerValue]; +} + +- (NSInteger)programId { + return [self.dictionary[M3U8_EXT_X_STREAM_INF_PROGRAM_ID] integerValue]; +} + +- (NSArray *)codecs { + NSString *codecsString = self.dictionary[M3U8_EXT_X_STREAM_INF_CODECS]; + return [codecsString componentsSeparatedByString:@","]; +} + +- (MediaResoulution)resolution { + NSString *rStr = self.dictionary[M3U8_EXT_X_STREAM_INF_RESOLUTION]; + MediaResoulution resolution = MediaResolutionFromString(rStr); + return resolution; +} + +- (NSString *)audio { + return self.dictionary[M3U8_EXT_X_STREAM_INF_AUDIO]; +} + +- (NSString *)video { + return self.dictionary[M3U8_EXT_X_STREAM_INF_VIDEO]; +} + +- (NSString *)subtitles { + return self.dictionary[M3U8_EXT_X_STREAM_INF_SUBTITLES]; +} + +- (NSString *)closedCaptions { + return self.dictionary[M3U8_EXT_X_STREAM_INF_CLOSED_CAPTIONS]; +} + +- (NSURL *)URI { + NSString *uriString = [self.dictionary[M3U8_EXT_X_STREAM_INF_URI] stringByReplacingOccurrencesOfString:@" " withString:@"%20"]; + return [NSURL URLWithString:uriString]; +} + +- (NSString *)description { + return [NSString stringWithString:self.dictionary.description]; +} + +/* + #EXT-X-STREAM-INF:AUDIO="600k",BANDWIDTH=1049794,AVERAGE-BANDWIDTH=1000000,PROGRAM-ID=1,CODECS="avc1.42c01e,mp4a.40.2",RESOLUTION=640x360,SUBTITLES="subs" + main_media_0.m3u8 + */ +- (NSString *)m3u8PlainString { + NSMutableString *str = [NSMutableString string]; + [str appendString:M3U8_EXT_X_STREAM_INF]; + if (self.audio.length > 0) { + [str appendString:[NSString stringWithFormat:@"AUDIO=\"%@\"", self.audio]]; + [str appendString:[NSString stringWithFormat:@",BANDWIDTH=%ld", (long)self.bandwidth]]; + } else { + [str appendString:[NSString stringWithFormat:@"BANDWIDTH=%ld", (long)self.bandwidth]]; + } + + if (self.averageBandwidth > 0) { + [str appendString:[NSString stringWithFormat:@",AVERAGE-BANDWIDTH=%ld", (long)self.averageBandwidth]]; + } + + [str appendString:[NSString stringWithFormat:@",PROGRAM-ID=%ld", (long)self.programId]]; + NSString *codecsString = self.dictionary[M3U8_EXT_X_STREAM_INF_CODECS]; + [str appendString:[NSString stringWithFormat:@",CODECS=\"%@\"", codecsString]]; + NSString *rStr = self.dictionary[M3U8_EXT_X_STREAM_INF_RESOLUTION]; + if (rStr.length > 0) { + [str appendString:[NSString stringWithFormat:@",RESOLUTION=%@", rStr]]; + } + [str appendString:[NSString stringWithFormat:@"\n%@", self.URI.absoluteString]]; + return str; +} + +@end + + + + + diff --git a/ios/Video/M3U8Kit/Source/M3U8ExtXStreamInfList.h b/ios/Video/M3U8Kit/Source/M3U8ExtXStreamInfList.h new file mode 100644 index 0000000000..dc0a48e77a --- /dev/null +++ b/ios/Video/M3U8Kit/Source/M3U8ExtXStreamInfList.h @@ -0,0 +1,23 @@ +// +// M3U8ExtXStreamInfList.h +// ILSLoader +// +// Created by Jin Sun on 13-4-15. +// Copyright (c) 2013年 iLegendSoft. All rights reserved. +// + +#import +#import "M3U8ExtXStreamInf.h" + +@interface M3U8ExtXStreamInfList : NSObject + +@property (nonatomic, assign ,readonly) NSUInteger count; + +- (void)addExtXStreamInf:(M3U8ExtXStreamInf *)extStreamInf; +- (M3U8ExtXStreamInf *)xStreamInfAtIndex:(NSUInteger)index; +- (M3U8ExtXStreamInf *)firstStreamInf; +- (M3U8ExtXStreamInf *)lastXStreamInf; + +- (void)sortByBandwidthInOrder:(NSComparisonResult)order; + +@end diff --git a/ios/Video/M3U8Kit/Source/M3U8ExtXStreamInfList.m b/ios/Video/M3U8Kit/Source/M3U8ExtXStreamInfList.m new file mode 100644 index 0000000000..505d904ae9 --- /dev/null +++ b/ios/Video/M3U8Kit/Source/M3U8ExtXStreamInfList.m @@ -0,0 +1,74 @@ +// +// M3U8ExtXStreamInfList.m +// ILSLoader +// +// Created by Jin Sun on 13-4-15. +// Copyright (c) 2013年 iLegendSoft. All rights reserved. +// + +#import "M3U8ExtXStreamInfList.h" + +@interface M3U8ExtXStreamInfList () + +@property (nonatomic, strong) NSMutableArray *m3u8InfoList; + +@end + +@implementation M3U8ExtXStreamInfList + +- (id)init { + self = [super init]; + if (self) { + self.m3u8InfoList = [NSMutableArray array]; + } + + return self; +} + +#pragma mark - Getter && Setter +- (NSUInteger)count { + return [self.m3u8InfoList count]; +} + +#pragma mark - Public +- (void)addExtXStreamInf:(M3U8ExtXStreamInf *)extStreamInf { + [self.m3u8InfoList addObject:extStreamInf]; +} + +- (M3U8ExtXStreamInf *)xStreamInfAtIndex:(NSUInteger)index { + if (index >= self.count) { + return nil; + } + return [self.m3u8InfoList objectAtIndex:index]; +} + +- (M3U8ExtXStreamInf *)firstStreamInf { + return [self.m3u8InfoList firstObject]; +} + +- (M3U8ExtXStreamInf *)lastXStreamInf { + return [self.m3u8InfoList lastObject]; +} + +- (void)sortByBandwidthInOrder:(NSComparisonResult)order { + + NSArray *array = [self.m3u8InfoList sortedArrayUsingComparator:^NSComparisonResult(id obj1, id obj2) { + NSInteger bandwidth1 = ((M3U8ExtXStreamInf *)obj1).bandwidth; + NSInteger bandwidth2 = ((M3U8ExtXStreamInf *)obj2).bandwidth; + if ( bandwidth1 == bandwidth2 ) { + return NSOrderedSame; + } else if (bandwidth1 < bandwidth2) { + return order; + } else { + return order * (-1); + } + }]; + + self.m3u8InfoList = [array mutableCopy]; +} + +- (NSString *)description { + return [NSString stringWithFormat:@"%@", self.m3u8InfoList]; +} + +@end diff --git a/ios/Video/M3U8Kit/Source/M3U8LineReader.h b/ios/Video/M3U8Kit/Source/M3U8LineReader.h new file mode 100644 index 0000000000..a000d01cdc --- /dev/null +++ b/ios/Video/M3U8Kit/Source/M3U8LineReader.h @@ -0,0 +1,18 @@ +// +// M3U8LineReader.h +// M3U8Kit +// +// Created by Noam Tamim on 22/03/2018. +// + +#import + +@interface M3U8LineReader : NSObject + +@property (nonatomic, readonly, strong) NSArray* lines; +@property (atomic, readonly, assign) NSUInteger index; + +- (instancetype)initWithText:(NSString*)text; +- (NSString*)next; + +@end diff --git a/ios/Video/M3U8Kit/Source/M3U8LineReader.m b/ios/Video/M3U8Kit/Source/M3U8LineReader.m new file mode 100644 index 0000000000..9cc49f465c --- /dev/null +++ b/ios/Video/M3U8Kit/Source/M3U8LineReader.m @@ -0,0 +1,33 @@ +// +// M3U8LineReader.m +// M3U8Kit +// +// Created by Noam Tamim on 22/03/2018. +// + +#import "M3U8LineReader.h" + + +@implementation M3U8LineReader +- (instancetype)initWithText:(NSString*)text +{ + self = [super init]; + if (self) { + _lines = [text componentsSeparatedByCharactersInSet:[NSCharacterSet newlineCharacterSet]]; + } + return self; +} + +- (NSString*)next { + while (_index < _lines.count) { + NSString* line = [_lines[_index] stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]]; + _index++; + + if (line.length > 0) { + return line; + } + } + return nil; +} +@end + diff --git a/ios/Video/M3U8Kit/Source/M3U8MasterPlaylist.h b/ios/Video/M3U8Kit/Source/M3U8MasterPlaylist.h new file mode 100644 index 0000000000..5d9059d360 --- /dev/null +++ b/ios/Video/M3U8Kit/Source/M3U8MasterPlaylist.h @@ -0,0 +1,38 @@ +// +// M3U8MasterPlaylist.h +// M3U8Kit +// +// Created by Sun Jin on 3/25/14. +// Copyright (c) 2014 Jin Sun. All rights reserved. +// + +#import +#import "M3U8ExtXStreamInfList.h" +#import "M3U8ExtXKey.h" +#import "M3U8ExtXMediaList.h" + +@interface M3U8MasterPlaylist : NSObject + +@property (nonatomic, strong) NSString *name; + +@property (readonly, nonatomic, strong) NSString *version; + +@property (readonly, nonatomic, copy) NSString *originalText; +@property (readonly, nonatomic, copy) NSURL *baseURL; +@property (readonly, nonatomic, copy) NSURL *originalURL; + +@property (readonly, nonatomic, strong) M3U8ExtXKey *xSessionKey; + +@property (readonly, nonatomic, strong) M3U8ExtXStreamInfList *xStreamList; +@property (readonly, nonatomic, strong) M3U8ExtXMediaList *xMediaList; + +- (NSArray *)allStreamURLs; + +- (M3U8ExtXStreamInfList *)alternativeXStreamInfList; + +- (instancetype)initWithContent:(NSString *)string baseURL:(NSURL *)baseURL; +- (instancetype)initWithContentOfURL:(NSURL *)URL error:(NSError **)error; + +- (NSString *)m3u8PlainString; + +@end diff --git a/ios/Video/M3U8Kit/Source/M3U8MasterPlaylist.m b/ios/Video/M3U8Kit/Source/M3U8MasterPlaylist.m new file mode 100644 index 0000000000..9deafb03ed --- /dev/null +++ b/ios/Video/M3U8Kit/Source/M3U8MasterPlaylist.m @@ -0,0 +1,223 @@ +// +// M3U8MasterPlaylist.m +// M3U8Kit +// +// Created by Sun Jin on 3/25/14. +// Copyright (c) 2014 Jin Sun. All rights reserved. +// + +#import "M3U8MasterPlaylist.h" +#import "NSString+m3u8.h" +#import "M3U8TagsAndAttributes.h" +#import "NSURL+m3u8.h" +#import "M3U8LineReader.h" + +// #define M3U8_EXT_X_STREAM_INF_CLOSED_CAPTIONS @"CLOSED-CAPTIONS" // The value can be either a quoted-string or an enumerated-string with the value NONE. +// NSArray *quotedValueAttrs = @[@"URI", @"KEYFORMAT", @"KEYFORMATVERSIONS", @"GROUP-ID", @"LANGUAGE", @"ASSOC-LANGUAGE", @"NAME", @"INSTREAM-ID", @"CHARACTERISTICS", @"CODECS", @"AUDIO", @"VIDEO", @"SUBTITLES", @"BYTERANGE"]; + +@interface M3U8MasterPlaylist () + +@property (nonatomic, copy) NSString *originalText; +@property (nonatomic, copy) NSURL *baseURL; +@property (nonatomic, copy) NSURL *originalURL; + +@property (nonatomic, strong) NSString *version; + +@property (nonatomic, strong) M3U8ExtXKey *xSessionKey; + +@property (nonatomic, strong) M3U8ExtXStreamInfList *xStreamList; +@property (nonatomic, strong) M3U8ExtXMediaList *xMediaList; + +@end + +@implementation M3U8MasterPlaylist + +- (instancetype)initWithContent:(NSString *)string baseURL:(NSURL *)baseURL { + if (!string.m3u_isMasterPlaylist) { + return nil; + } + if (self = [super init]) { + self.originalText = string; + self.baseURL = baseURL; + [self parseMasterPlaylist]; + } + return self; +} + +- (instancetype)initWithContentOfURL:(NSURL *)URL error:(NSError **)error { + if (!URL) { + return nil; + } + + self.originalURL = URL; + + NSString *string = [NSString stringWithContentsOfURL:URL encoding:NSUTF8StringEncoding error:error]; + return [self initWithContent:string baseURL:URL.m3u_realBaseURL]; +} + +- (void)parseMasterPlaylist { + + self.xStreamList = [[M3U8ExtXStreamInfList alloc] init]; + self.xMediaList = [[M3U8ExtXMediaList alloc] init]; + + M3U8LineReader* reader = [[M3U8LineReader alloc] initWithText:self.originalText]; + + while (true) { + + NSString* line = [reader next]; + if (!line) { + break; + } + + // #EXT-X-VERSION:4 + if ([line hasPrefix:M3U8_EXT_X_VERSION]) { + NSRange r_version = [line rangeOfString:M3U8_EXT_X_VERSION]; + self.version = [line substringFromIndex:r_version.location + r_version.length]; + } + + else if ([line hasPrefix:M3U8_EXT_X_SESSION_KEY]) { + NSRange range = [line rangeOfString:M3U8_EXT_X_SESSION_KEY]; + NSString *attribute_list = [line substringFromIndex:range.location + range.length]; + NSMutableDictionary *attr = attribute_list.m3u_attributesFromAssignmentByComma; + + M3U8ExtXKey *sessionKey = [[M3U8ExtXKey alloc] initWithDictionary:attr]; + self.xSessionKey = sessionKey; + } + + // #EXT-X-STREAM-INF:AUDIO="600k",BANDWIDTH=915685,PROGRAM-ID=1,CODECS="avc1.42c01e,mp4a.40.2",RESOLUTION=640x360,SUBTITLES="subs" + // http://hls.ted.com/talks/769/video/600k.m3u8?sponsor=Ripple + else if ([line hasPrefix:M3U8_EXT_X_STREAM_INF]) { + NSRange range = [line rangeOfString:M3U8_EXT_X_STREAM_INF]; + NSString *attribute_list = [line substringFromIndex:range.location + range.length]; + NSMutableDictionary *attr = attribute_list.m3u_attributesFromAssignmentByComma; + + NSString *nextLine = [reader next]; + attr[@"URI"] = nextLine; + if (self.originalURL) { + attr[M3U8_URL] = self.originalURL; + } + + if (self.baseURL) { + attr[M3U8_BASE_URL] = self.baseURL; + } + + M3U8ExtXStreamInf *xStreamInf = [[M3U8ExtXStreamInf alloc] initWithDictionary:attr]; + [self.xStreamList addExtXStreamInf:xStreamInf]; + } + + + // Ignore the following tag, which is not implemented yet. + // #EXT-X-I-FRAME-STREAM-INF:BANDWIDTH=65531,PROGRAM-ID=1,CODECS="avc1.42c00c",RESOLUTION=320x180,URI="/talks/769/video/64k_iframe.m3u8?sponsor=Ripple" + else if ([line hasPrefix:M3U8_EXT_X_I_FRAME_STREAM_INF]) { + + + } + + // #EXT-X-MEDIA:TYPE=AUDIO,GROUP-ID="600k",LANGUAGE="eng",NAME="Audio",AUTOSELECT=YES,DEFAULT=YES,URI="/talks/769/audio/600k.m3u8?sponsor=Ripple",BANDWIDTH=614400 + else if ([line hasPrefix:M3U8_EXT_X_MEDIA]) { + NSRange range = [line rangeOfString:M3U8_EXT_X_MEDIA]; + NSString *attribute_list = [line substringFromIndex:range.location + range.length]; + NSMutableDictionary *attr = attribute_list.m3u_attributesFromAssignmentByComma; + if (self.baseURL.absoluteString.length > 0) { + attr[M3U8_BASE_URL] = self.baseURL; + } + + if (self.originalURL.absoluteString.length > 0) { + attr[M3U8_URL] = self.originalURL; + } + M3U8ExtXMedia *media = [[M3U8ExtXMedia alloc] initWithDictionary:attr]; + [self.xMediaList addExtXMedia:media]; + } + } +} + +- (NSArray *)allStreamURLs { + NSMutableArray *array = [NSMutableArray array]; + for (int i = 0; i < self.xStreamList.count; i ++) { + M3U8ExtXStreamInf *xsinf = [self.xStreamList xStreamInfAtIndex:i]; + if (xsinf.m3u8URL.absoluteString.length > 0) { + if (NO == [array containsObject:xsinf.m3u8URL]) { + [array addObject:xsinf.m3u8URL]; + } + } + } + return [array copy]; +} + +- (M3U8ExtXStreamInfList *)alternativeXStreamInfList { + + M3U8ExtXStreamInfList *list = [[M3U8ExtXStreamInfList alloc] init]; + + M3U8ExtXStreamInfList *xsilist = self.xStreamList; + for (int index = 0; index < xsilist.count; index ++) { + M3U8ExtXStreamInf *xsinf = [xsilist xStreamInfAtIndex:index]; + BOOL flag = NO; + for (NSString *str in xsinf.codecs) { + if (NO == flag) { + flag = [str hasPrefix:@"avc1"]; + } + } + if (flag) { + [list addExtXStreamInf:xsinf]; + } + } + + // It is only used when the resolution is selected. + // M3U8ExtXMediaList *xmlist = self.masterPlaylist.xMediaList.videoList; + // for (int i = 0; i < xmlist.count; i ++) { + // M3U8ExtXMedia *media = [xmlist extXMediaAtIndex:i]; + // [allAlternativeURLStrings addObject:media.m3u8URL]; + // } + return list; +} + +- (NSString *)m3u8PlainString { + NSMutableString *str = [NSMutableString string]; + [str appendString:M3U8_EXTM3U]; + [str appendString:@"\n"]; + if (self.version.length > 0) { + [str appendString:[NSString stringWithFormat:@"%@%@", M3U8_EXT_X_VERSION, self.version]]; + [str appendString:@"\n"]; + } + for (NSInteger index = 0; index < self.xStreamList.count; index ++) { + M3U8ExtXStreamInf *xsinf = [self.xStreamList xStreamInfAtIndex:index]; + [str appendString:xsinf.m3u8PlainString]; + [str appendString:@"\n"]; + } + + M3U8ExtXMediaList *audioList = self.xMediaList.audioList; + for (NSInteger i = 0; i < audioList.count; i ++) { + NSLog(@"ext x media %ld", (long)i); + M3U8ExtXMedia *media = [audioList xMediaAtIndex:i]; + [str appendString:media.m3u8PlainString]; + [str appendString:@"\n"]; + } + + return str; +} + +@end + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/ios/Video/M3U8Kit/Source/M3U8MediaPlaylist.h b/ios/Video/M3U8Kit/Source/M3U8MediaPlaylist.h new file mode 100644 index 0000000000..c22a3aeca5 --- /dev/null +++ b/ios/Video/M3U8Kit/Source/M3U8MediaPlaylist.h @@ -0,0 +1,42 @@ +// +// M3U8MediaPlaylist.h +// M3U8Kit +// +// Created by Sun Jin on 3/26/14. +// Copyright (c) 2014 Jin Sun. All rights reserved. +// + +#import +#import "M3U8SegmentInfoList.h" + +typedef enum { + M3U8MediaPlaylistTypeMedia = 0, // The main media stream playlist. + M3U8MediaPlaylistTypeSubtitle = 1, // EXT-X-SUBTITLES TYPE=SUBTITLES + M3U8MediaPlaylistTypeAudio = 2, // EXT-X-MEDIA TYPE=AUDIO + M3U8MediaPlaylistTypeVideo = 3 // EXT-X-MEDIA TYPE=VIDEO +} M3U8MediaPlaylistType; + +@interface M3U8MediaPlaylist : NSObject + +@property (nonatomic, strong) NSString *name; + +@property (readonly, nonatomic, strong) NSString *version; + +@property (readonly, nonatomic, copy) NSString *originalText; +@property (readonly, nonatomic, copy) NSURL *baseURL; + +@property (readonly, nonatomic, copy) NSURL *originalURL; + +@property (readonly, nonatomic, strong) M3U8SegmentInfoList *segmentList; + +/** live or replay */ +@property (assign, readonly, nonatomic) BOOL isLive; + +@property (nonatomic) M3U8MediaPlaylistType type; // -1 by default + +- (instancetype)initWithContent:(NSString *)string type:(M3U8MediaPlaylistType)type baseURL:(NSURL *)baseURL; +- (instancetype)initWithContentOfURL:(NSURL *)URL type:(M3U8MediaPlaylistType)type error:(NSError **)error; + +- (NSArray *)allSegmentURLs; + +@end diff --git a/ios/Video/M3U8Kit/Source/M3U8MediaPlaylist.m b/ios/Video/M3U8Kit/Source/M3U8MediaPlaylist.m new file mode 100644 index 0000000000..78e1cc6fe3 --- /dev/null +++ b/ios/Video/M3U8Kit/Source/M3U8MediaPlaylist.m @@ -0,0 +1,150 @@ +// +// M3U8MediaPlaylist.m +// M3U8Kit +// +// Created by Sun Jin on 3/26/14. +// Copyright (c) 2014 Jin Sun. All rights reserved. +// + +#import "M3U8MediaPlaylist.h" +#import "NSString+m3u8.h" +#import "M3U8TagsAndAttributes.h" +#import "NSURL+m3u8.h" +#import "M3U8LineReader.h" +#import "M3U8ExtXKey.h" +#import "M3U8ExtXByteRange.h" +#import "NSArray+m3u8.h" + +@interface M3U8MediaPlaylist() + +@property (nonatomic, copy) NSString *originalText; +@property (nonatomic, copy) NSURL *baseURL; +@property (nonatomic, copy) NSURL *originalURL; + +@property (nonatomic, strong) NSString *version; + +@property (nonatomic, strong) M3U8SegmentInfoList *segmentList; + +@property (assign, nonatomic) BOOL isLive; + +@end + +@implementation M3U8MediaPlaylist + +- (instancetype)initWithContent:(NSString *)string type:(M3U8MediaPlaylistType)type baseURL:(NSURL *)baseURL { + if (!string.m3u_isMediaPlaylist) { + return nil; + } + + if (self = [super init]) { + self.originalText = string; + self.baseURL = baseURL; + self.type = type; + [self parseMediaPlaylist]; + } + return self; +} + +- (instancetype)initWithContentOfURL:(NSURL *)URL type:(M3U8MediaPlaylistType)type error:(NSError **)error { + if (nil == URL) { + return nil; + } + + self.originalURL = URL; + + NSString *string = [[NSString alloc] initWithContentsOfURL:URL encoding:NSUTF8StringEncoding error:error]; + + return [self initWithContent:string type:type baseURL:URL.m3u_realBaseURL]; +} + +- (NSArray *)allSegmentURLs { + NSMutableArray *array = [NSMutableArray array]; + for (int i = 0; i < self.segmentList.count; i ++) { + M3U8SegmentInfo *info = [self.segmentList segmentInfoAtIndex:i]; + if (info.mediaURL.absoluteString.length > 0) { + if (NO == [array containsObject:info.mediaURL]) { + [array addObject:info.mediaURL]; + } + } + } + return [array copy]; +} + +- (void)parseMediaPlaylist +{ + self.segmentList = [[M3U8SegmentInfoList alloc] init]; + BOOL isLive = [self.originalText rangeOfString:M3U8_EXT_X_ENDLIST].location == NSNotFound; + self.isLive = isLive; + + M3U8LineReader* reader = [[M3U8LineReader alloc] initWithText:self.originalText]; + M3U8ExtXKey *key = nil; + + while (true) { + + NSString* line = [reader next]; + if (!line) { + break; + } + + NSMutableDictionary *params = [[NSMutableDictionary alloc] init]; + if (self.originalURL) { + [params setObject:self.originalURL forKey:M3U8_URL]; + } + + if (self.baseURL) { + [params setObject:self.baseURL forKey:M3U8_BASE_URL]; + } + + if ([line hasPrefix:M3U8_EXT_X_KEY]) { + line = [line stringByReplacingOccurrencesOfString:M3U8_EXT_X_KEY withString:@""]; + key = [[M3U8ExtXKey alloc] initWithDictionary:line.m3u_attributesFromAssignmentByComma]; + } + + //check if it's #EXTINF: + if ([line hasPrefix:M3U8_EXTINF]) { + line = [line stringByReplacingOccurrencesOfString:M3U8_EXTINF withString:@""]; + + NSArray *components = [line componentsSeparatedByString:@","]; + NSString *info = components.firstObject; + if (info) { + NSString *blankMark = @" "; + NSArray *additions = [info componentsSeparatedByString:blankMark]; + // get duration + NSString *duration = additions.firstObject; + params[M3U8_EXTINF_DURATION] = duration; + + // get additional parameters from Extended M3U https://en.wikipedia.org/wiki/M3U#Extended_M3U + if (additions.count > 1) { + // no need remove duration(first element). `m3u_attributesFromAssignmentByMark` function will skip first non-equation value. + params[M3U8_EXTINF_ADDITIONAL_PARAMETERS] = [additions m3u_attributesFromAssignmentByMark:blankMark]; + } + } + if (components.count > 1) { + params[M3U8_EXTINF_TITLE] = components[1]; + } + + line = reader.next; + // read ByteRange. only for version 4 + M3U8ExtXByteRange *byteRange = nil; + if ([line hasPrefix:M3U8_EXT_X_BYTERANGE]) { + line = [line stringByReplacingOccurrencesOfString:M3U8_EXT_X_BYTERANGE withString:@""]; + byteRange = [[M3U8ExtXByteRange alloc] initWithAtString:line]; + line = reader.next; + } + //ignore other # message + while ([line hasPrefix:@"#"]) { + line = reader.next; + } + //then get URI + params[M3U8_EXTINF_URI] = line; + + M3U8SegmentInfo *segment = [[M3U8SegmentInfo alloc] initWithDictionary:params xKey:key byteRange:byteRange]; + if (segment) { + [self.segmentList addSegementInfo:segment]; + } + } + } +} + +@end + diff --git a/ios/Video/M3U8Kit/Source/M3U8Parser.h b/ios/Video/M3U8Kit/Source/M3U8Parser.h new file mode 100644 index 0000000000..d35d760cb7 --- /dev/null +++ b/ios/Video/M3U8Kit/Source/M3U8Parser.h @@ -0,0 +1,25 @@ +// +// M3U8Parser.h +// M3U8Parser +// +// Created by Frank on 20-4-16. +// Copyright (c) 2020年 M3U8Kit. All rights reserved. +// + +#import "M3U8ExtXStreamInf.h" +#import "M3U8ExtXStreamInfList.h" +#import "M3U8ExtXKey.h" +#import "M3U8ExtXMedia.h" +#import "M3U8ExtXMediaList.h" +#import "M3U8SegmentInfo.h" +#import "M3U8SegmentInfoList.h" + +#import "M3U8MasterPlaylist.h" +#import "M3U8MediaPlaylist.h" +#import "M3U8PlaylistModel.h" + +#import "NSString+m3u8.h" +#import "NSURL+m3u8.h" +#import "NSArray+m3u8.h" +#import "M3U8ExtXByteRange.h" +#import "M3U8TagsAndAttributes.h" diff --git a/ios/Video/M3U8Kit/Source/M3U8Parser.modulemap b/ios/Video/M3U8Kit/Source/M3U8Parser.modulemap new file mode 100644 index 0000000000..fd5b9dee2e --- /dev/null +++ b/ios/Video/M3U8Kit/Source/M3U8Parser.modulemap @@ -0,0 +1,6 @@ +framework module M3U8Parser { + umbrella header "M3U8Parser.h" + + export * + module * { export * } +} diff --git a/ios/Video/M3U8Kit/Source/M3U8PlaylistModel.h b/ios/Video/M3U8Kit/Source/M3U8PlaylistModel.h new file mode 100644 index 0000000000..062a498184 --- /dev/null +++ b/ios/Video/M3U8Kit/Source/M3U8PlaylistModel.h @@ -0,0 +1,90 @@ +// +// M3U8PlaylistModel.h +// M3U8Kit +// +// Created by Oneday on 13-1-11. +// Copyright (c) 2013年 0day. All rights reserved. +// + +#import +#import "M3U8MasterPlaylist.h" +#import "M3U8MediaPlaylist.h" + +// 用来管理 m3u playlist, 根据 URL 或者 string 生成 master playlist, 从master playlist 生成指定的 media playlist +// 生成 master playlist +// 提取默认的 media playlist,包括 video segments, subtitles playlist, audio playlist +// 取出media playlist 里面的链接等信息 + +// 把 master playlist 和 media playlist 中的链接替换 合成为本地服务器可用的playlist + + +// 此版本为简化版,只考虑 #EXT-X-STREAM-INF Tag 中的信息,其他忽略掉 + +/** + + 需要下载的内容: + + --- master playlist 中的内容,如果有的话 + 1. master playlist + 2. 默认的 media playlist, 由第一个 #EXT-X-STREAM-INF Tag 指定 + 3. 音频 如果有一个 #EXT-X-STREAM-INF Tag 的codecs只有音频部分,则认为它的URI指向一个音频文件,应该下载下来,其它的Tag 暂时都简单的忽略掉 + + . 下载各 media playlist 对应的分段内容 + + */ + +@interface M3U8PlaylistModel : NSObject + +@property (readonly, nonatomic, copy) NSURL *baseURL; +@property (readonly, nonatomic, copy) NSURL *originalURL; + +// 如果初始化时的字符串是 media playlist, masterPlaylist为nil +// M3U8PlaylistModel 默认会按照《需要下载的内容》规则选取默认的playlist,如果需要手动指定获取特定的media playlist, 需调用方法 -specifyVideoURL:(这个在选取视频源的时候会用到); + +@property (readonly, nonatomic, strong) M3U8MasterPlaylist *masterPlaylist; + +@property (readonly, nonatomic, strong) M3U8MediaPlaylist *mainMediaPl; +- (void)changeMainMediaPlWithPlaylist:(M3U8MediaPlaylist *)playlist; +@property (readonly, nonatomic, strong) M3U8MediaPlaylist *audioPl; +//@property (readonly, nonatomic, strong) M3U8MediaPlaylist *subtitlePl; + +/** + this method is synchronous. so may be **block your thread** that call this method. + + @param URL M3U8 URL + @param error error pointer + @return playlist model + */ +- (id)initWithURL:(NSURL *)URL error:(NSError **)error; +- (id)initWithString:(NSString *)string baseURL:(NSURL *)URL error:(NSError **)error; +- (id)initWithString:(NSString *)string originalURL:(NSURL *)originalURL + baseURL:(NSURL *)baseURL error:(NSError * *)error; + +// 改变 mainMediaPl +// 这个url必须是master playlist 中多码率url(绝对地址)中的一个 +// 这个方法先会去获取url中的内容,生成一个mediaPlaylist,如果内容获取失败,mainMediaPl改变失败 +- (void)specifyVideoURL:(NSURL *)URL completion:(void (^)(BOOL success))completion; + +- (NSString *)indexPlaylistName; + +- (NSString *)prefixOfSegmentNameInPlaylist:(M3U8MediaPlaylist *)playlist; +- (NSString *)sufixOfSegmentNameInPlaylist:(M3U8MediaPlaylist *)playlist; + +- (NSArray *)segmentNamesForPlaylist:(M3U8MediaPlaylist *)playlist; + +// segment name will be formatted as ["%@%d.%@", prefix, index, sufix] eg. main_media_1.ts +- (void)savePlaylistsToPath:(NSString *)path error:(NSError **)error; + +@end + + + + + + + + + + + + diff --git a/ios/Video/M3U8Kit/Source/M3U8PlaylistModel.m b/ios/Video/M3U8Kit/Source/M3U8PlaylistModel.m new file mode 100644 index 0000000000..75c6f1de6f --- /dev/null +++ b/ios/Video/M3U8Kit/Source/M3U8PlaylistModel.m @@ -0,0 +1,297 @@ +// +// M3U8Parser.m +// M3U8Kit +// +// Created by Oneday on 13-1-11. +// Copyright (c) 2013年 0day. All rights reserved. +// + +#import "M3U8PlaylistModel.h" +#import "NSString+m3u8.h" +#import "NSURL+m3u8.h" + +#define INDEX_PLAYLIST_NAME @"index.m3u8" + +#define PREFIX_MAIN_MEDIA_PLAYLIST @"main_media_" +#define PREFIX_AUDIO_PLAYLIST @"x_media_audio_" +#define PREFIX_SUBTITLES_PLAYLIST @"x_media_subtitles_" + +@interface M3U8PlaylistModel() + +@property (nonatomic, copy) NSURL *baseURL; +@property (nonatomic, copy) NSURL *originalURL; + +@property (nonatomic, strong) M3U8MasterPlaylist *masterPlaylist; + +@property (nonatomic, strong) M3U8ExtXStreamInf *currentXStreamInf; + +@property (nonatomic, strong) M3U8MediaPlaylist *mainMediaPl; +@property (nonatomic, strong) M3U8MediaPlaylist *audioPl; +//@property (nonatomic, strong) M3U8MediaPlaylist *subtitlePl; + +@end + +@implementation M3U8PlaylistModel + +- (id)initWithURL:(NSURL *)URL error:(NSError **)error { + + NSString *str = [[NSString alloc] initWithContentsOfURL:URL encoding:NSUTF8StringEncoding error:error]; + if (*error) { + return nil; + } + + self.originalURL = URL; + + return [self initWithString:str baseURL:URL.m3u_realBaseURL error:error]; +} + +- (id)initWithString:(NSString *)string baseURL:(NSURL *)baseURL error:(NSError **)error { + return [self initWithString:string originalURL:nil baseURL:baseURL error:error]; +} + +- (id)initWithString:(NSString *)string originalURL:(NSURL *)originalURL + baseURL:(NSURL *)baseURL error:(NSError * *)error { + + if (!string.m3u_isExtendedM3Ufile) { + *error = [NSError errorWithDomain:@"M3U8PlaylistModel" code:-998 userInfo:@{NSLocalizedDescriptionKey:@"The content is not a m3u8 playlist"}]; + return nil; + } + + if (self = [super init]) { + if (string.m3u_isMasterPlaylist) { + self.originalURL = originalURL; + self.baseURL = baseURL; + self.masterPlaylist = [[M3U8MasterPlaylist alloc] initWithContent:string baseURL:baseURL]; + self.masterPlaylist.name = INDEX_PLAYLIST_NAME; + self.currentXStreamInf = self.masterPlaylist.xStreamList.firstStreamInf; + if (self.currentXStreamInf) { + NSError *ero; + NSURL *m3u8URL = self.currentXStreamInf.m3u8URL; + self.mainMediaPl = [[M3U8MediaPlaylist alloc] initWithContentOfURL:m3u8URL type:M3U8MediaPlaylistTypeMedia error:&ero]; + self.mainMediaPl.name = [NSString stringWithFormat:@"%@0.m3u8", PREFIX_MAIN_MEDIA_PLAYLIST]; + if (ero) { + NSLog(@"Get main media playlist failed, error: %@", ero); + } + } + + // get audioPl + M3U8ExtXStreamInfList *list = self.masterPlaylist.xStreamList; + if (list.count > 1) { + for (int i = 0; i < list.count; i++) { + M3U8ExtXStreamInf *xsinf = [list xStreamInfAtIndex:i]; + if (xsinf.codecs.count == 1 && [xsinf.codecs.firstObject hasPrefix:@"mp4a"]) { + NSURL *audioURL = xsinf.m3u8URL; + self.audioPl = [[M3U8MediaPlaylist alloc] initWithContentOfURL:audioURL type:M3U8MediaPlaylistTypeAudio error:NULL]; + self.audioPl.name = [NSString stringWithFormat:@"%@%d.m3u8", PREFIX_MAIN_MEDIA_PLAYLIST, i]; + break; + } + } + } + + } else if (string.m3u_isMediaPlaylist) { + self.mainMediaPl = [[M3U8MediaPlaylist alloc] initWithContent:string type:M3U8MediaPlaylistTypeMedia baseURL:baseURL]; + self.mainMediaPl.name = INDEX_PLAYLIST_NAME; + } + } + return self; +} + +- (NSSet *)allAlternativeURLStrings { + NSMutableSet *allAlternativeURLStrings = [NSMutableSet set]; + M3U8ExtXStreamInfList *xsilist = self.masterPlaylist.alternativeXStreamInfList; + for (int index = 0; index < xsilist.count; index ++) { + M3U8ExtXStreamInf *xsinf = [xsilist xStreamInfAtIndex:index]; + [allAlternativeURLStrings addObject:xsinf.m3u8URL]; + } + + return allAlternativeURLStrings; +} + +- (void)specifyVideoURL:(NSURL *)URL completion:(void (^)(BOOL))completion { + + dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ + + BOOL success = NO; + + if (URL.absoluteString.length > 0 + && nil != self.masterPlaylist + && [self.allAlternativeURLStrings containsObject:URL]) { + + if ([URL.absoluteString isEqualToString:self.mainMediaPl.originalURL.absoluteString]) { + success = YES; + } else { + NSError *error; + M3U8MediaPlaylist *pl = [[M3U8MediaPlaylist alloc] initWithContentOfURL:URL type:M3U8MediaPlaylistTypeMedia error:&error]; + if (pl) { + self.mainMediaPl = pl; + M3U8ExtXStreamInfList *list = self.masterPlaylist.xStreamList; + if (list.count > 1) { + for (int i = 0; i < list.count; i++) { + M3U8ExtXStreamInf *xsinf = [list xStreamInfAtIndex:i]; + if ([xsinf.m3u8URL.absoluteString isEqualToString:pl.originalURL.absoluteString]) { + pl.name = [NSString stringWithFormat:@"%@%d.m3u8", PREFIX_MAIN_MEDIA_PLAYLIST, i]; + break; + } + } + } + success = YES; + } + } + } + + dispatch_async(dispatch_get_main_queue(), ^{ + if (completion) { + completion(success); + } + }); + }); +} + +- (void)changeMainMediaPlWithPlaylist:(M3U8MediaPlaylist *)playlist { + if (playlist + && playlist.type == M3U8MediaPlaylistTypeMedia + && [[self allAlternativeURLStrings] containsObject:playlist.baseURL]) { + + self.mainMediaPl = playlist; + M3U8ExtXStreamInfList *list = self.masterPlaylist.xStreamList; + if (list.count > 1) { + for (int i = 0; i < list.count; i++) { + M3U8ExtXStreamInf *xsinf = [list xStreamInfAtIndex:i]; + if ([xsinf.m3u8URL.absoluteString isEqualToString:playlist.originalURL.absoluteString]) { + playlist.name = [NSString stringWithFormat:@"%@%d.m3u8", PREFIX_MAIN_MEDIA_PLAYLIST, i]; + break; + } + } + } + } +} + +- (NSString *)prefixOfSegmentNameInPlaylist:(M3U8MediaPlaylist *)playlist { + NSString *prefix = nil; + + switch (playlist.type) { + case M3U8MediaPlaylistTypeMedia: + prefix = @"media_"; + break; + case M3U8MediaPlaylistTypeAudio: + prefix = @"audio_"; + break; + case M3U8MediaPlaylistTypeSubtitle: + prefix = @"subtitle_"; + break; + case M3U8MediaPlaylistTypeVideo: + prefix = @"video_"; + break; + + default: + return @""; + break; + } + return prefix; +} + +- (NSString *)sufixOfSegmentNameInPlaylist:(M3U8MediaPlaylist *)playlist { + NSString *prefix = nil; + + switch (playlist.type) { + case M3U8MediaPlaylistTypeMedia: + case M3U8MediaPlaylistTypeVideo: + prefix = @"ts"; + break; + case M3U8MediaPlaylistTypeAudio: + prefix = @"aac"; + break; + case M3U8MediaPlaylistTypeSubtitle: + prefix = @"vtt"; + break; + + default: + return @""; + break; + } + return prefix; +} + +- (NSArray *)segmentNamesForPlaylist:(M3U8MediaPlaylist *)playlist { + + NSString *prefix = [self prefixOfSegmentNameInPlaylist:playlist]; + NSString *sufix = [self sufixOfSegmentNameInPlaylist:playlist]; + NSMutableArray *names = [NSMutableArray array]; + + NSArray *URLs = playlist.allSegmentURLs; + NSUInteger count = playlist.segmentList.count; + NSUInteger index = 0; + for (int i = 0; i < count; i ++) { + M3U8SegmentInfo *inf = [playlist.segmentList segmentInfoAtIndex:i]; + index = [URLs indexOfObject:inf.mediaURL]; + NSString *n = [NSString stringWithFormat:@"%@%lu.%@", prefix, (unsigned long)index, sufix]; + [names addObject:n]; + } + return names; +} + +- (void)savePlaylistsToPath:(NSString *)path error:(NSError **)error { + + if ([[NSFileManager defaultManager] fileExistsAtPath:path]) { + if (NO == [[NSFileManager defaultManager] removeItemAtPath:path error:error]) { + return; + } + } + if (NO == [[NSFileManager defaultManager] createDirectoryAtPath:path withIntermediateDirectories:YES attributes:nil error:error]) { + return; + } + + if (self.masterPlaylist) { + + // master playlist + NSString *masterContext = self.masterPlaylist.m3u8PlainString; + for (int i = 0; i < self.masterPlaylist.xStreamList.count; i ++) { + M3U8ExtXStreamInf *xsinf = [self.masterPlaylist.xStreamList xStreamInfAtIndex:i]; + NSString *name = [NSString stringWithFormat:@"%@%d.m3u8", PREFIX_MAIN_MEDIA_PLAYLIST, i]; + masterContext = [masterContext stringByReplacingOccurrencesOfString:xsinf.URI.absoluteString withString:name]; + } + NSString *mPath = [path stringByAppendingPathComponent:self.indexPlaylistName]; + BOOL success = [masterContext writeToFile:mPath atomically:YES encoding:NSUTF8StringEncoding error:error]; + if (NO == success) { + NSLog(@"M3U8Kit Error: failed to save master playlist to file. error: %@", error?*error:@"null"); + return; + } + + // main media playlist + [self saveMediaPlaylist:self.mainMediaPl toPath:path error:error]; + [self saveMediaPlaylist:self.audioPl toPath:path error:error]; + + } else { + [self saveMediaPlaylist:self.mainMediaPl toPath:path error:error]; + } +} + +- (void)saveMediaPlaylist:(M3U8MediaPlaylist *)playlist toPath:(NSString *)path error:(NSError **)error { + if (nil == playlist) { + return; + } + NSString *mainMediaPlContext = playlist.originalText; + if (mainMediaPlContext.length == 0) { + return; + } + + NSArray *names = [self segmentNamesForPlaylist:playlist]; + for (int i = 0; i < playlist.segmentList.count; i ++) { + M3U8SegmentInfo *sinfo = [playlist.segmentList segmentInfoAtIndex:i]; + mainMediaPlContext = [mainMediaPlContext stringByReplacingOccurrencesOfString:sinfo.URI.absoluteString withString:names[i]]; + } + NSString *mainMediaPlPath = [path stringByAppendingPathComponent:playlist.name]; + BOOL success = [mainMediaPlContext writeToFile:mainMediaPlPath atomically:YES encoding:NSUTF8StringEncoding error:error]; + if (NO == success) { + if (NULL != error) { + NSLog(@"M3U8Kit Error: failed to save mian media playlist to file. error: %@", *error); + } + return; + } +} + +- (NSString *)indexPlaylistName { + return INDEX_PLAYLIST_NAME; +} + +@end diff --git a/ios/Video/M3U8Kit/Source/M3U8SegmentInfo.h b/ios/Video/M3U8Kit/Source/M3U8SegmentInfo.h new file mode 100644 index 0000000000..10c0db967c --- /dev/null +++ b/ios/Video/M3U8Kit/Source/M3U8SegmentInfo.h @@ -0,0 +1,45 @@ +// +// M3U8SegmentInfo.h +// M3U8Kit +// +// Created by Oneday on 13-1-11. +// Copyright (c) 2013年 0day. All rights reserved. +// + +#import + +@class M3U8ExtXKey, M3U8ExtXByteRange; +/*! + @class M3U8SegmentInfo + @abstract This is the class indicates #EXTINF:, + media in m3u8 file + + +@format #EXTINF:<duration>,<title> + +#define M3U8_EXTINF @"#EXTINF:" + +#define M3U8_EXTINF_DURATION @"DURATION" +#define M3U8_EXTINF_TITLE @"TITLE" +#define M3U8_EXTINF_URI @"URI" +#define M3U8_EXT_X_KEY @"#EXT-X-KEY:" + + */ +@interface M3U8SegmentInfo : NSObject + +@property (readonly, nonatomic) NSTimeInterval duration; +@property (readonly, nonatomic, copy) NSString *title; +@property (readonly, nonatomic, copy) NSURL *URI; +@property (readonly, nonatomic, strong) M3U8ExtXByteRange *byteRange; +/** Key for media data decrytion. may be for this segment or next if no key. */ +@property (readonly, nonatomic, strong) M3U8ExtXKey *xKey; +@property (readonly, nonatomic, strong) NSDictionary<NSString *, NSString *> *additionalParameters; + +- (instancetype)initWithDictionary:(NSDictionary *)dictionary; + +- (instancetype)initWithDictionary:(NSDictionary *)dictionary xKey:(M3U8ExtXKey *)key; + +- (instancetype)initWithDictionary:(NSDictionary *)dictionary xKey:(M3U8ExtXKey *)key byteRange:(M3U8ExtXByteRange *)byteRange NS_DESIGNATED_INITIALIZER; + +- (NSURL *)mediaURL; + +@end diff --git a/ios/Video/M3U8Kit/Source/M3U8SegmentInfo.m b/ios/Video/M3U8Kit/Source/M3U8SegmentInfo.m new file mode 100644 index 0000000000..55cbd015ec --- /dev/null +++ b/ios/Video/M3U8Kit/Source/M3U8SegmentInfo.m @@ -0,0 +1,84 @@ +// +// M3U8SegmentInfo.m +// M3U8Kit +// +// Created by Oneday on 13-1-11. +// Copyright (c) 2013年 0day. All rights reserved. +// + +#import "M3U8SegmentInfo.h" +#import "M3U8TagsAndAttributes.h" +#import "M3U8ExtXKey.h" +#import "M3U8ExtXByteRange.h" + +@interface M3U8SegmentInfo() +@property (nonatomic, strong) NSDictionary *dictionary; + +@end + +@implementation M3U8SegmentInfo + +@synthesize xKey = _xKey; + +- (instancetype)init { + return [self initWithDictionary:nil xKey:nil]; +} + +- (instancetype)initWithDictionary:(NSDictionary *)dictionary { + return [self initWithDictionary:dictionary xKey:nil]; +} + +- (instancetype)initWithDictionary:(NSDictionary *)dictionary xKey:(M3U8ExtXKey *)key { + return [self initWithDictionary:dictionary xKey:key byteRange:nil]; +} + +- (instancetype)initWithDictionary:(NSDictionary *)dictionary xKey:(M3U8ExtXKey *)key byteRange:(M3U8ExtXByteRange *)byteRange{ + if (self = [super init]) { + _dictionary = dictionary; + _xKey = key; + _byteRange = byteRange; + } + return self; +} + +- (NSURL *)baseURL { + return self.dictionary[M3U8_BASE_URL]; +} + +- (NSURL *)URL { + return self.dictionary[M3U8_URL]; +} + +- (NSURL *)mediaURL { + if (self.URI.scheme) { + return self.URI; + } + + return [NSURL URLWithString:self.URI.absoluteString relativeToURL:[self baseURL]]; +} + +- (NSTimeInterval)duration { + return [self.dictionary[M3U8_EXTINF_DURATION] doubleValue]; +} + +- (NSString *)title { + return self.dictionary[M3U8_EXTINF_TITLE]; +} + +- (NSURL *)URI { + NSString *originalUrl = self.dictionary[M3U8_EXTINF_URI]; + NSString *urlString = [originalUrl stringByReplacingOccurrencesOfString:@" " withString:@"%20"]; + return [NSURL URLWithString:urlString]; +} + +- (NSDictionary<NSString *,NSString *> *)additionalParameters { + return self.dictionary[M3U8_EXTINF_ADDITIONAL_PARAMETERS]; +} + +- (NSString *)description { + NSMutableDictionary *dict = [self.dictionary mutableCopy]; + [dict addEntriesFromDictionary:[self.xKey valueForKey:@"dictionary"]]; + return dict.description; +} + +@end diff --git a/ios/Video/M3U8Kit/Source/M3U8SegmentInfoList.h b/ios/Video/M3U8Kit/Source/M3U8SegmentInfoList.h new file mode 100644 index 0000000000..a8bdc0751b --- /dev/null +++ b/ios/Video/M3U8Kit/Source/M3U8SegmentInfoList.h @@ -0,0 +1,19 @@ +// +// M3U8SegmentInfoList.h +// M3U8Kit +// +// Created by Oneday on 13-1-11. +// Copyright (c) 2013年 0day. All rights reserved. +// + +#import <Foundation/Foundation.h> +#import "M3U8SegmentInfo.h" + +@interface M3U8SegmentInfoList : NSObject + +@property (nonatomic, assign ,readonly) NSUInteger count; + +- (void)addSegementInfo:(M3U8SegmentInfo *)segment; +- (M3U8SegmentInfo *)segmentInfoAtIndex:(NSUInteger)index; + +@end diff --git a/ios/Video/M3U8Kit/Source/M3U8SegmentInfoList.m b/ios/Video/M3U8Kit/Source/M3U8SegmentInfoList.m new file mode 100644 index 0000000000..b30b45f280 --- /dev/null +++ b/ios/Video/M3U8Kit/Source/M3U8SegmentInfoList.m @@ -0,0 +1,46 @@ +// +// M3U8SegmentInfoList.m +// M3U8Kit +// +// Created by Oneday on 13-1-11. +// Copyright (c) 2013年 0day. All rights reserved. +// + +#import "M3U8SegmentInfoList.h" + +@interface M3U8SegmentInfoList () + +@property (nonatomic, strong) NSMutableArray *segmentInfoList; + +@end + +@implementation M3U8SegmentInfoList + +- (id)init { + if (self = [super init]) { + self.segmentInfoList = [NSMutableArray array]; + } + return self; +} + +#pragma mark - Getter && Setter +- (NSUInteger)count { + return [self.segmentInfoList count]; +} + +#pragma mark - Public +- (void)addSegementInfo:(M3U8SegmentInfo *)segment { + if (segment) { + [self.segmentInfoList addObject:segment]; + } +} + +- (M3U8SegmentInfo *)segmentInfoAtIndex:(NSUInteger)index { + return [self.segmentInfoList objectAtIndex:index]; +} + +- (NSString *)description { + return [NSString stringWithFormat:@"%@", self.segmentInfoList]; +} + +@end diff --git a/ios/Video/M3U8Kit/Source/M3U8TagsAndAttributes.h b/ios/Video/M3U8Kit/Source/M3U8TagsAndAttributes.h new file mode 100644 index 0000000000..75117669d3 --- /dev/null +++ b/ios/Video/M3U8Kit/Source/M3U8TagsAndAttributes.h @@ -0,0 +1,308 @@ +// +// M3U8TagsAndAttributes.h +// M3U8Kit +// +// Created by Sun Jin on 3/24/14. +// Copyright (c) 2014 Jin Sun. All rights reserved. +// + + +// M3U8 Tags & Attributes definded in Draft Pantos Http Live Streaming 12 http://tools.ietf.org/html/draft-pantos-http-live-streaming-12 + +#define M3U8_URL @"URL" + +#define M3U8_BASE_URL @"baseURL" + + +///------------------------------ +/// Standard Tags +///------------------------------ + +#define M3U8_EXTM3U @"#EXTM3U" + +/** + @format #EXTINF:<duration> <additional parameters> ...,<title> + */ +#define M3U8_EXTINF @"#EXTINF:" +#define M3U8_EXTINF_DURATION @"DURATION" +#define M3U8_EXTINF_TITLE @"TITLE" +#define M3U8_EXTINF_URI @"URI" +#define M3U8_EXTINF_ADDITIONAL_PARAMETERS @"ADDITIONAL_PARAMETERS" + + +/// NEW TAGS +/** + @format #EXT-X-BYTERANGE:<n>[@<o>] + + @note The EXT-X-BYTERANGE tag appeared in version 4 of the protocol. It MUST NOT appear in a Master Playlist. + */ +#define M3U8_EXT_X_BYTERANGE @"#EXT-X-BYTERANGE:" + + +/** + @format #EXT-X-TARGETDURATION:<s> + + @note The EXT-X-TARGETDURATION tag MUST NOT appear in a Master Playlist. + */ +#define M3U8_EXT_X_TARGETDURATION @"#EXT-X-TARGETDURATION:" + + +/** + @format #EXT-X-MEDIA-SEQUENCE:<number> + @note The EXT-X-MEDIA-SEQUENCE tag MUST NOT appear in a Master Playlist. + */ +#define M3U8_EXT_X_MEDIA_SEQUENCE @"#EXT-X-MEDIA-SEQUENCE:" + +/// EXT-X-KEY +/** + @format #EXT-X-KEY:<attribute-list> ps: We may need download the key file at URI. + */ +#define M3U8_EXT_X_KEY @"#EXT-X-KEY:" +// EXT-X-KEY Attributes +#define M3U8_EXT_X_KEY_METHOD @"METHOD" // The value is an enumerated-string that specifies the encryption method. This attribute is REQUIRED. + // The methods defined are: NONE, AES-128, and SAMPLE-AES. +#define M3U8_EXT_X_KEY_URI @"URI" // The value is a quoted-string containing a URI [RFC3986] that specifies how to obtain the key. This attribute is REQUIRED unless the METHOD is NONE. +#define M3U8_EXT_X_KEY_IV @"IV" // The value is a hexadecimal-integer that specifies the Initialization Vector to be used with the key. +#define M3U8_EXT_X_KEY_KEYFORMAT @"KEYFORMAT" // The value is a quoted-string that specifies how the key is represented in the resource identified by the URI +#define M3U8_EXT_X_KEY_KEYFORMATVERSIONS @"KEYFORMATVERSIONS" // The value is a quoted-string containing one or more positive integers separated by the "/" character (for example, "1/3"). + +/// M3U8_EXT_X_SESSION_KEY +/** + Preload EXT-X-KEY infomations + + @format #EXT-X-SESSION-KEY:<attribute list> + + All attributes defined for the EXT-X-KEY tag (Section 4.3.2.4) are + also defined for the EXT-X-SESSION-KEY, except that the value of the + METHOD attribute MUST NOT be NONE. If an EXT-X-SESSION-KEY is used, + the values of the METHOD, KEYFORMAT and KEYFORMATVERSIONS attributes + MUST match any EXT-X-KEY with the same URI value. + + EXT-X-SESSION-KEY tags SHOULD be added if multiple Variant Streams or + Renditions use the same encryption keys and formats. A EXT-X + -SESSION-KEY tag is not associated with any particular Media + Playlist. + + A Master Playlist MUST NOT contain more than one EXT-X-SESSION-KEY + tag with the same METHOD, URI, IV, KEYFORMAT, and KEYFORMATVERSIONS + attribute values. + + The EXT-X-SESSION-KEY tag is optional.. + */ +#define M3U8_EXT_X_SESSION_KEY @"#EXT-X-SESSION-KEY:" + +/// M3U8_EXT_X_SESSION_DATA +/** + Some customized data between Server & Client + The EXT-X-SESSION-DATA tag allows arbitrary session data to be + carried in a Master Playlist. + + @format #EXT-X-SESSION-DATA:<attribute list> . + @example #EXT-X-SESSION-DATA:DATA-ID="com.example.lyrics",URI="lyrics.json" + @example #EXT-X-SESSION-DATA:DATA-ID="com.example.title",LANGUAGE="en",VALUE="This is an example" + + + The following attributes are defined: + + DATA-ID + + The value of DATA-ID is a quoted-string which identifies that data + value. The DATA-ID SHOULD conform to a reverse DNS naming + convention, such as "com.example.movie.title"; however, there is + no central registration authority, so Playlist authors SHOULD take + care to choose a value which is unlikely to collide with others. + This attribute is REQUIRED. + + VALUE + + VALUE is a quoted-string. It contains the data identified by + DATA-ID. If the LANGUAGE is specified, VALUE SHOULD contain a + human-readable string written in the specified language. + + URI + + The value is a quoted-string containing a URI. The resource + identified by the URI MUST be formatted as JSON [RFC7159]; + otherwise, clients may fail to interpret the resource. + + LANGUAGE + + The value is a quoted-string containing a language tag [RFC5646] + that identifies the language of the VALUE. This attribute is + OPTIONAL. + + Each EXT-X-SESSION-DATA tag MUST contain either a VALUE or URI + attribute, but not both. + + A Playlist MAY contain multiple EXT-X-SESSION-DATA tags with the same + DATA-ID attribute. A Playlist MUST NOT contain more than one EXT-X + -SESSION-DATA tag with the same DATA-ID attribute and the same + LANGUAGE attribute. + */ +#define M3U8_EXT_X_SESSION_DATA @"EXT-X-SESSION-DATA:" + + +/** + @format: #EXT-X-PROGRAM-DATE-TIME:<YYYY-MM-DDThh:mm:ssZ> + @note The EXT-X-PROGRAM-DATE-TIME tag MUST NOT appear in a Master Playlist. + */ +#define M3U8_EXT_X_PROGRAM_DATE_TIME @"#EXT-X-PROGRAM-DATE-TIME:" + + +/** + @format: #EXT-X-ALLOW-CACHE:<YES|NO> + @note It MUST NOT occur more than once. + */ +#define M3U8_EXT_X_ALLOW_CACHE @"#EXT-X-ALLOW-CACHE:" + + +/** + @format: #EXT-X-PLAYLIST-TYPE:<EVENT|VOD> + @note The EXT-X-PLAYLIST-TYPE tag MUST NOT appear in a Master Playlist. + */ +#define M3U8_EXT_X_PLAYLIST_TYPE @"#EXT-X-PLAYLIST-TYPE:" + + +/** + @format: #EXT-X-ENDLIST ps: it MUST NOT occur more than once. + @note The EXT-X-ENDLIST tag MUST NOT appear in a Master Playlist. + */ +#define M3U8_EXT_X_ENDLIST @"#EXT-X-ENDLIST" + + + +/// EXT-X-MEDIA +/** + @format #EXT-X-MEDIA:<attribute-list> , attibute-list: ATTR=<value>,... + @example #EXT-X-MEDIA:TYPE=AUDIO,GROUP-ID="600k",LANGUAGE="eng",NAME="Audio",AUTOSELECT=YES,DEFAULT=YES,URI="/talks/769/audio/600k.m3u8?sponsor=Ripple",BANDWIDTH=614400 + */ +#define M3U8_EXT_X_MEDIA @"#EXT-X-MEDIA:" +// EXT-X-MEDIA attributes +#define M3U8_EXT_X_MEDIA_TYPE @"TYPE" // The value is enumerated-string; valid strings are AUDIO, VIDEO, SUBTITLES and CLOSED-CAPTIONS. +#define M3U8_EXT_X_MEDIA_URI @"URI" // The value is a quoted-string containing a URI that identifies the Playlist file. +#define M3U8_EXT_X_MEDIA_GROUP_ID @"GROUP-ID" // The value is a quoted-string identifying a mutually-exclusive group of renditions. +#define M3U8_EXT_X_MEDIA_LANGUAGE @"LANGUAGE" // The value is a quoted-string containing an RFC 5646 [RFC5646] language tag that identifies the primary language used in the rendition. +#define M3U8_EXT_X_MEDIA_ASSOC_LANGUAGE @"ASSOC-LANGUAGE" // The value is a quoted-string containing an RFC 5646 [RFC5646](http://tools.ietf.org/html/rfc5646) language tag that identifies a language that is associated with the rendition. +#define M3U8_EXT_X_MEDIA_NAME @"NAME" // The value is a quoted-string containing a human-readable description of the rendition. +#define M3U8_EXT_X_MEDIA_DEFAULT @"DEFAULT" // The value is an enumerated-string; valid strings are YES and NO. +#define M3U8_EXT_X_MEDIA_AUTOSELECT @"AUTOSELECT" // The value is an enumerated-string; valid strings are YES and NO. +#define M3U8_EXT_X_MEDIA_FORCED @"FORCED" // The value is an enumerated-string; valid strings are YES and NO. +#define M3U8_EXT_X_MEDIA_INSTREAM_ID @"INSTREAM-ID" // The value is a quoted-string that specifies a rendition within the segments in the Media Playlist. +#define M3U8_EXT_X_MEDIA_CHARACTERISTICS @"CHARACTERISTICS" // The value is a quoted-string containing one or more Uniform Type Identifiers [UTI] separated by comma (,) characters. +#define M3U8_EXT_X_MEDIA_BANDWIDTH @"BANDWIDTH" + + +/// EXT-X-STREAM-INF +/** + @format #EXT-X-STREAM-INF:<attribute-list> + <URI> + @example #EXT-X-STREAM-INF:AUDIO="600k",BANDWIDTH=915685,PROGRAM-ID=1,CODECS="avc1.42c01e,mp4a.40.2",RESOLUTION=640x360,SUBTITLES="subs" + /talks/769/video/600k.m3u8?sponsor=Ripple + + @note The EXT-X-STREAM-INF tag MUST NOT appear in a Media Playlist. + */ +#define M3U8_EXT_X_STREAM_INF @"#EXT-X-STREAM-INF:" +// EXT-X-STREAM-INF Attributes +#define M3U8_EXT_X_STREAM_INF_BANDWIDTH @"BANDWIDTH" // The value is a decimal-integer of bits per second. +#define M3U8_EXT_X_STREAM_INF_AVERAGE_BANDWIDTH @"AVERAGE-BANDWIDTH" // The value is a decimal-integer of bits per second. It represents the average segment bit rate of the Variant Stream. +#define M3U8_EXT_X_STREAM_INF_PROGRAM_ID @"PROGRAM-ID" // The value is a decimal-integer that uniquely identifies a particular presentation within the scope of the Playlist file. +#define M3U8_EXT_X_STREAM_INF_CODECS @"CODECS" // The value is a quoted-string containing a comma-separated list of formats. +#define M3U8_EXT_X_STREAM_INF_RESOLUTION @"RESOLUTION" // The value is a decimal-resolution describing the approximate encoded horizontal and vertical resolution of video within the presentation. +#define M3U8_EXT_X_STREAM_INF_AUDIO @"AUDIO" // The value is a quoted-string. +#define M3U8_EXT_X_STREAM_INF_VIDEO @"VIDEO" // The value is a quoted-string. +#define M3U8_EXT_X_STREAM_INF_SUBTITLES @"SUBTITLES" // The value is a quoted-string. +#define M3U8_EXT_X_STREAM_INF_CLOSED_CAPTIONS @"CLOSED-CAPTIONS" // The value can be either a quoted-string or an enumerated-string with the value NONE. +#define M3U8_EXT_X_STREAM_INF_URI @"URI" // The value is a enumerated-string containing a URI that identifies the Playlist file. + + + +/** + @format #EXT-X-DISCONTINUITY + @note The EXT-X-DISCONTINUITY tag MUST NOT appear in a Master Playlist. + */ +#define M3U8_EXT_X_DISCONTINUITY @"#EXT-X-DISCONTINUITY" + + +/** + @format #EXT-X-DISCONTINUITY-SEQUENCE:<number> where number is a decimal-integer. + @note The discontinuity sequence number MUST NOT decrease. + A Media Playlist MUST NOT contain more than one EXT-X-DISCONTINUITY-SEQUENCE tag. + */ +#define M3U8_EXT_X_DISCONTINUITY_SEQUENCE @"#EXT-X-DISCONTINUITY-SEQUENCE:" + + +/** + @format #EXT-X-I-FRAMES-ONLY + @note The EXT-X-I-FRAMES-ONLY tag MUST NOT appear in a Master Playlist.(v4) + */ +#define M3U8_EXT_X_I_FRAMES_ONLY @"#EXT-X-I-FRAMES-ONLY" + + + +/// EXT-X-MAP +/** + @format #EXT-X-MAP:<attribute-list> + @note The EXT-X-MAP tag MUST NOT appear in a Master Playlist. + */ +#define M3U8_EXT_X_MAP @"#EXT-X-MAP:" +// EXT-X-MAP attributes +#define M3U8_EXT_X_MAP_URI @"URI" // The value is a quoted-string containing a URI that identifies a resource that contains segment header information. This attribute is REQUIRED. +#define M3U8_EXT_X_MAP_BYTERANGE @"BYTERANGE" // The value is a quoted-string specifying a byte range into the resource identified by the URI attribute. + + + +/// EXT-X-I-FRAME-STREAM-INF +/** + @format #EXT-X-I-FRAME-STREAM-INF:<attribute-list> + @example #EXT-X-I-FRAME-STREAM-INF:BANDWIDTH=65531,PROGRAM-ID=1,CODECS="avc1.42c00c",RESOLUTION=320x180,URI="/talks/769/video/64k_iframe.m3u8?sponsor=Ripple" + + @note All attributes defined for the EXT-X-STREAM-INF tag (Section 3.4.10) are also defined for the EXT-X-I-FRAME-STREAM-INF tag, except for the AUDIO, SUBTITLES and CLOSED-CAPTIONS attributes. + The EXT-X-I-FRAME-STREAM-INF tag MUST NOT appear in a Media Playlist. + */ +#define M3U8_EXT_X_I_FRAME_STREAM_INF @"#EXT-X-I-FRAME-STREAM-INF:" +// EXT-X-I-FRAME-STREAM-INF Attributes +#define M3U8_EXT_X_I_FRAME_STREAM_INF_URI @"URI" // The value is a quoted-string containing a URI that identifies the I-frame Playlist file. +#define M3U8_EXT_X_I_FRAME_STREAM_INF_BANDWIDTH @"BANDWIDTH" // The value is a decimal-integer of bits per second. +#define M3U8_EXT_X_I_FRAME_STREAM_INF_PROGRAM_ID @"PROGRAM-ID" // The value is a decimal-integer that uniquely identifies a particular presentation within the scope of the Playlist file. +#define M3U8_EXT_X_I_FRAME_STREAM_INF_CODECS @"CODECS" // The value is a quoted-string containing a comma-separated list of formats. +#define M3U8_EXT_X_I_FRAME_STREAM_INF_RESOLUTION @"RESOLUTION" // The value is a decimal-resolution describing the approximate encoded horizontal and vertical resolution of video within the presentation. +#define M3U8_EXT_X_I_FRAME_STREAM_INF_VIDEO @"VIDEO" // The value is a quoted-string. + + + + +/// EXT-X-START +/** + @format #EXT-X-START:<attribute list> + */ +#define M3U8_EXT_X_START @"#EXT-X-START:" +// EXT-X-START Attributes +#define M3U8_EXT_X_START_TIME_OFFSET @"TIME-OFFSET" // The value of TIME-OFFSET is a decimal-floating-point number of seconds. +#define M3U8_EXT_X_START_PRECISE @"PRECISE" // The value is an enumerated-string; valid strings are YES and NO. + + + + + +/** + @format #EXT-X-VERSION:<n> where n is an integer indicating the protocol version. + */ +#define M3U8_EXT_X_VERSION @"#EXT-X-VERSION:" + + + + + +/** + @format #EXT-X-SESSION-KEY:<n> where n is an integer indicating the protocol version. + */ +#define M3U8_EXT_X_SESSION_KEY @"#EXT-X-SESSION-KEY:" + + + + + + + + + diff --git a/ios/Video/M3U8Kit/Source/NSArray+m3u8.h b/ios/Video/M3U8Kit/Source/NSArray+m3u8.h new file mode 100644 index 0000000000..d61cbe3730 --- /dev/null +++ b/ios/Video/M3U8Kit/Source/NSArray+m3u8.h @@ -0,0 +1,28 @@ +// +// NSArray+m3u8.h +// M3U8Kit +// +// Created by Frank on 2022/7/12. +// Copyright © 2022 M3U8Kit. All rights reserved. +// + +#import <Foundation/Foundation.h> + +NS_ASSUME_NONNULL_BEGIN + +@interface NSArray (m3u8) + +/** @return "key=value" transform to dictionary */ +- (NSMutableDictionary *)m3u_attributesFromAssignment; + +/** + If check by invalid value, value will append to last element with specific mark. + + @param mark attribute will be ignored if it is invalid. + @return "key=value" transform to dictionary + */ +- (NSMutableDictionary *)m3u_attributesFromAssignmentByMark:(nullable NSString *)mark; + +@end + +NS_ASSUME_NONNULL_END diff --git a/ios/Video/M3U8Kit/Source/NSArray+m3u8.m b/ios/Video/M3U8Kit/Source/NSArray+m3u8.m new file mode 100644 index 0000000000..27041225d0 --- /dev/null +++ b/ios/Video/M3U8Kit/Source/NSArray+m3u8.m @@ -0,0 +1,43 @@ +// +// NSArray+m3u8.m +// M3U8Kit +// +// Created by Frank on 2022/7/12. +// Copyright © 2022 M3U8Kit. All rights reserved. +// + +#import "NSArray+m3u8.h" +#import "NSString+m3u8.h" + +@implementation NSArray (m3u8) + +- (NSMutableDictionary *)m3u_attributesFromAssignment { + return [self m3u_attributesFromAssignmentByMark:nil]; +} + +- (NSMutableDictionary *)m3u_attributesFromAssignmentByMark:(NSString *)mark { + NSMutableDictionary *dict = [NSMutableDictionary dictionary]; + + NSString *lastkey = nil; + for (NSString *keyValue in self) { + NSRange equalMarkRange = [keyValue rangeOfString:@"="]; + // if equal mark is not found, it means this value is previous value left. eg: CODECS=\"avc1.42c01e,mp4a.40.2\" + if (equalMarkRange.location == NSNotFound) { + if (!mark) continue; + if (!lastkey) continue; + NSString *lastValue = dict[lastkey]; + NSString *supplement = [lastValue stringByAppendingFormat:@"%@%@", mark, keyValue.m3u_stringByTrimmingQuoteMark]; + dict[lastkey] = supplement; + continue; + } + NSString *key = [keyValue substringToIndex:equalMarkRange.location].m3u_stringByTrimmingQuoteMark; + NSString *value = [keyValue substringFromIndex:equalMarkRange.location + 1].m3u_stringByTrimmingQuoteMark; + + dict[key] = value; + lastkey = key; + } + + return dict; +} + +@end diff --git a/ios/Video/M3U8Kit/Source/NSString+m3u8.h b/ios/Video/M3U8Kit/Source/NSString+m3u8.h new file mode 100644 index 0000000000..519423f05c --- /dev/null +++ b/ios/Video/M3U8Kit/Source/NSString+m3u8.h @@ -0,0 +1,30 @@ +// +// NSString+m3u8.h +// M3U8Kit +// +// Created by Oneday on 13-1-11. +// Copyright (c) 2013年 0day. All rights reserved. +// + +#import <Foundation/Foundation.h> + +@class M3U8ExtXStreamInfList, M3U8SegmentInfoList; +@interface NSString (m3u8) + +- (BOOL)m3u_isExtendedM3Ufile; + +- (BOOL)m3u_isMasterPlaylist; +- (BOOL)m3u_isMediaPlaylist; + +- (M3U8SegmentInfoList *)m3u_segementInfoListValueRelativeToURL:(NSString *)baseURL; + +/** + @return "key=value" transform to dictionary + */ +- (NSMutableDictionary *)m3u_attributesFromAssignmentByMark:(NSString *)mark; +- (NSMutableDictionary *)m3u_attributesFromAssignmentByComma; +- (NSMutableDictionary *)m3u_attributesFromAssignmentByBlank; + +- (NSString *)m3u_stringByTrimmingQuoteMark; + +@end diff --git a/ios/Video/M3U8Kit/Source/NSString+m3u8.m b/ios/Video/M3U8Kit/Source/NSString+m3u8.m new file mode 100644 index 0000000000..b41f917c02 --- /dev/null +++ b/ios/Video/M3U8Kit/Source/NSString+m3u8.m @@ -0,0 +1,148 @@ +// +// NSString+m3u8.m +// M3U8Kit +// +// Created by Oneday on 13-1-11. +// Copyright (c) 2013年 0day. All rights reserved. +// + +#import "NSString+m3u8.h" +#import "M3U8SegmentInfo.h" +#import "M3U8SegmentInfoList.h" +#import "M3U8ExtXStreamInf.h" +#import "M3U8ExtXStreamInfList.h" + +#import "M3U8TagsAndAttributes.h" +#import "NSArray+m3u8.h" + +@implementation NSString (m3u8) + +/** + The Extended M3U file format defines two tags: EXTM3U and EXTINF. An + Extended M3U file is distinguished from a basic M3U file by its first + line, which MUST be #EXTM3U. + + reference url:http://tools.ietf.org/html/draft-pantos-http-live-streaming-00 + */ +- (BOOL)m3u_isExtendedM3Ufile { + return [self hasPrefix:M3U8_EXTM3U]; +} + +- (BOOL)m3u_isMasterPlaylist { + BOOL isM3U = [self m3u_isExtendedM3Ufile]; + if (isM3U) { + NSRange r1 = [self rangeOfString:M3U8_EXT_X_STREAM_INF]; + NSRange r2 = [self rangeOfString:M3U8_EXT_X_I_FRAME_STREAM_INF]; + if (r1.location != NSNotFound || r2.location != NSNotFound) { + return YES; + } + } + return NO; +} + +- (BOOL)m3u_isMediaPlaylist { + BOOL isM3U = [self m3u_isExtendedM3Ufile]; + if (isM3U) { + NSRange r = [self rangeOfString:M3U8_EXTINF]; + if (r.location != NSNotFound) { + return YES; + } + } + return NO; +} + +- (M3U8SegmentInfoList *)m3u_segementInfoListValueRelativeToURL:(NSString *)baseURL { + // self == @"" + if (0 == self.length) + return nil; + + /** + The Extended M3U file format defines two tags: EXTM3U and EXTINF. An + Extended M3U file is distinguished from a basic M3U file by its first + line, which MUST be #EXTM3U. + + reference url:http://tools.ietf.org/html/draft-pantos-http-live-streaming-00 + */ + NSRange rangeOfEXTM3U = [self rangeOfString:M3U8_EXTM3U]; + if (rangeOfEXTM3U.location == NSNotFound || + rangeOfEXTM3U.location != 0) { + return nil; + } + + M3U8SegmentInfoList *segmentInfoList = [[M3U8SegmentInfoList alloc] init]; + + NSRange segmentRange = [self rangeOfString:M3U8_EXTINF]; + NSString *remainingSegments = self; + + while (NSNotFound != segmentRange.location) { + NSMutableDictionary *params = [[NSMutableDictionary alloc] init]; + if (baseURL) { + [params setObject:baseURL forKey:M3U8_BASE_URL]; + } + + // Read the EXTINF number between #EXTINF: and the comma + NSRange commaRange = [remainingSegments rangeOfString:@","]; + NSRange valueRange = NSMakeRange(segmentRange.location + 8, commaRange.location - (segmentRange.location + 8)); + if (commaRange.location == NSNotFound || valueRange.location > remainingSegments.length -1) + break; + + NSString *value = [remainingSegments substringWithRange:valueRange]; + [params setValue:value forKey:M3U8_EXTINF_DURATION]; + + // ignore the #EXTINF line + remainingSegments = [remainingSegments substringFromIndex:segmentRange.location]; + NSRange extinfoLFRange = [remainingSegments rangeOfString:@"\n"]; + remainingSegments = [remainingSegments substringFromIndex:extinfoLFRange.location + 1]; + + // Read the segment link, and ignore line start with # && blank line + while (1) { + NSRange lfRange = [remainingSegments rangeOfString:@"\n"]; + NSString *line = [remainingSegments substringWithRange:NSMakeRange(0, lfRange.location)]; + line = [line stringByReplacingOccurrencesOfString:@" " withString:@""]; + + remainingSegments = [remainingSegments substringFromIndex:lfRange.location + 1]; + + if ([line characterAtIndex:0] != '#' && 0 != line.length) { + // remove the CR character '\r' + unichar lastChar = [line characterAtIndex:line.length - 1]; + if (lastChar == '\r') { + line = [line substringToIndex:line.length - 1]; + } + + [params setValue:line forKey:M3U8_EXTINF_URI]; + break; + } + } + + M3U8SegmentInfo *segment = [[M3U8SegmentInfo alloc] initWithDictionary:params]; + if (segment) { + [segmentInfoList addSegementInfo:segment]; + } + + segmentRange = [remainingSegments rangeOfString:M3U8_EXTINF]; + } + + return segmentInfoList; +} + +- (NSString *)m3u_stringByTrimmingQuoteMark { + NSCharacterSet *quoteMarkCharactersSet = [NSCharacterSet characterSetWithCharactersInString:@"\"' "]; + NSString *string = [self stringByTrimmingCharactersInSet:quoteMarkCharactersSet]; + return string; +} + +- (NSMutableDictionary *)m3u_attributesFromAssignmentByComma { + return [self m3u_attributesFromAssignmentByMark:@","]; +} + +- (NSMutableDictionary *)m3u_attributesFromAssignmentByBlank { + return [self m3u_attributesFromAssignmentByMark:@" "]; +} + +- (NSMutableDictionary *)m3u_attributesFromAssignmentByMark:(NSString *)mark { + NSArray<NSString *> *keyValues = [self componentsSeparatedByString:mark]; + + return [keyValues m3u_attributesFromAssignmentByMark:mark]; +} + +@end diff --git a/ios/Video/M3U8Kit/Source/NSURL+m3u8.h b/ios/Video/M3U8Kit/Source/NSURL+m3u8.h new file mode 100644 index 0000000000..57c6d5a041 --- /dev/null +++ b/ios/Video/M3U8Kit/Source/NSURL+m3u8.h @@ -0,0 +1,28 @@ +// +// NSURL+m3u8.h +// M3U8Kit +// +// Created by Frank on 16/06/2017. +// + +#import <Foundation/Foundation.h> + +@class M3U8PlaylistModel; +@interface NSURL (m3u8) + +/** + return baseURL if exists. + if baseURL is nil, return [scheme://host] + + @return URL + */ +- (NSURL *)m3u_realBaseURL; + +/** + Load the specific url and get result model with completion block. + + @param completion when the url resource loaded, completion block could get model and detail error; + */ +- (void)m3u_loadAsyncCompletion:(void (^)(M3U8PlaylistModel *model, NSError *error))completion; + +@end diff --git a/ios/Video/M3U8Kit/Source/NSURL+m3u8.m b/ios/Video/M3U8Kit/Source/NSURL+m3u8.m new file mode 100644 index 0000000000..d4f0b60d94 --- /dev/null +++ b/ios/Video/M3U8Kit/Source/NSURL+m3u8.m @@ -0,0 +1,46 @@ +// +// NSURL+m3u8.m +// M3U8Kit +// +// Created by Frank on 16/06/2017. +// + +#import "NSURL+m3u8.h" +#import "M3U8PlaylistModel.h" + +@implementation NSURL (m3u8) + +- (NSURL *)m3u_realBaseURL { + NSURL *baseURL = self.baseURL; + if (!baseURL) { + NSString *string = [self.absoluteString stringByReplacingOccurrencesOfString:self.lastPathComponent withString:@""]; + + baseURL = [NSURL URLWithString:string]; + } + + return baseURL; +} + +- (void)m3u_loadAsyncCompletion:(void (^)(M3U8PlaylistModel *model, NSError *error))completion { + dispatch_async(dispatch_get_global_queue(QOS_CLASS_USER_INTERACTIVE, 0), ^{ + NSError *err = nil; + NSString *str = [[NSString alloc] initWithContentsOfURL:self + encoding:NSUTF8StringEncoding error:&err]; + + if (err) { + completion(nil, err); + return; + } + + M3U8PlaylistModel *listModel = [[M3U8PlaylistModel alloc] initWithString:str + originalURL:self baseURL:self.m3u_realBaseURL error:&err]; + if (err) { + completion(nil, err); + return; + } + + completion(listModel, nil); + }); +} + +@end diff --git a/ios/Video/RCTVideo-Bridging-Header.h b/ios/Video/RCTVideo-Bridging-Header.h index a01ef01390..e3492cd937 100644 --- a/ios/Video/RCTVideo-Bridging-Header.h +++ b/ios/Video/RCTVideo-Bridging-Header.h @@ -1,6 +1,22 @@ #import "RCTVideoSwiftLog.h" #import <React/RCTViewManager.h> #import "CurrentVideos.h" +#import "NSURL+m3u8.h" +#import "M3U8ExtXByteRange.h" +#import "M3U8ExtXKey.h" +#import "M3U8ExtXMedia.h" +#import "M3U8ExtXMediaList.h" +#import "M3U8ExtXStreamInf.h" +#import "M3U8ExtXStreamInfList.h" +#import "M3U8LineReader.h" +#import "M3U8MasterPlaylist.h" +#import "M3U8MediaPlaylist.h" +#import "M3U8PlaylistModel.h" +#import "M3U8SegmentInfo.h" +#import "M3U8SegmentInfoList.h" +#import "M3U8TagsAndAttributes.h" +#import "NSArray+m3u8.h" +#import "NSString+m3u8.h" #if __has_include(<react-native-video/RCTVideoCache.h>) #import "RCTVideoCache.h" diff --git a/ios/Video/RCTVideo.swift b/ios/Video/RCTVideo.swift index ff57f1c93a..41db2ceca4 100644 --- a/ios/Video/RCTVideo.swift +++ b/ios/Video/RCTVideo.swift @@ -140,6 +140,7 @@ class RCTVideo: UIView, RCTVideoPlayerViewControllerDelegate, RCTPlayerObserverH @objc var onPlaybackStalled: RCTDirectEventBlock? @objc var onPlaybackResume: RCTDirectEventBlock? @objc var onPlaybackRateChange: RCTDirectEventBlock? + @objc var onPlayedTracksChange: RCTDirectEventBlock? @objc var onVolumeChange: RCTDirectEventBlock? @objc var onVideoPlaybackStateChanged: RCTDirectEventBlock? @objc var onVideoExternalPlaybackChange: RCTDirectEventBlock? @@ -628,10 +629,51 @@ class RCTVideo: UIView, RCTVideoPlayerViewControllerDelegate, RCTPlayerObserverH playerItem.navigationMarkerGroups = RCTVideoTVUtils.makeNavigationMarkerGroups(chapters) } #endif - + let url = ((playerItem.asset as? AVURLAsset)?.url as? NSURL) + + url?.m3u_loadAsyncCompletion { model, error in + if model != nil { + self.onPlayedTracksChange?( + [ + "audioTrack": self.getAudioTrackInfo(model: model), + "textTrack": self._textTracks + ] + ) + } + } return playerItem } + func getAudioTrackInfo(model: M3U8PlaylistModel?) -> [String: Any] { + guard let model else { return .init() } + let group: AVMediaSelectionGroup = _player?.currentItem? + .asset.mediaSelectionGroup(forMediaCharacteristic: .audible) ?? .init() + + if group.options.count > 0 { + let currentOption: AVMediaSelectionOption = group.options[0] + + var title: String? + let values = currentOption.commonMetadata.map(\.value) + + if values.count > 0 { + title = values[0] as? String + } + + let language: String = currentOption.extendedLanguageTag ?? "" + + let file: String = model.originalURL.lastPathComponent + + return [ + "index": NSNumber(0), + "title": title, + "language": language, + "file": file + ] + } + + return .init() + } + // MARK: - Prop setters @objc diff --git a/ios/Video/RCTVideoManager.m b/ios/Video/RCTVideoManager.m index c3c968c3ca..b80736afe1 100644 --- a/ios/Video/RCTVideoManager.m +++ b/ios/Video/RCTVideoManager.m @@ -59,6 +59,7 @@ @interface RCT_EXTERN_MODULE (RCTVideoManager, RCTViewManager) RCT_EXPORT_VIEW_PROPERTY(onPlaybackStalled, RCTDirectEventBlock); RCT_EXPORT_VIEW_PROPERTY(onPlaybackResume, RCTDirectEventBlock); RCT_EXPORT_VIEW_PROPERTY(onPlaybackRateChange, RCTDirectEventBlock); +RCT_EXPORT_VIEW_PROPERTY(onPlayedTracksChange, RCTDirectEventBlock); RCT_EXPORT_VIEW_PROPERTY(onVolumeChange, RCTDirectEventBlock); RCT_EXPORT_VIEW_PROPERTY(onVideoPlaybackStateChanged, RCTDirectEventBlock); RCT_EXPORT_VIEW_PROPERTY(onVideoExternalPlaybackChange, RCTDirectEventBlock); From 4605fefb88e2f7e9763c5206b2238950c4b6b9fc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pawel=20Lozi=C5=84ski?= <pawel.lozinski@miquido.com> Date: Thu, 13 Jun 2024 17:02:35 +0200 Subject: [PATCH 10/37] from: Add podspec with m3u8 parser commit: b2d2ecf --- ios/Video/RCTVideo.swift | 3 +++ 1 file changed, 3 insertions(+) diff --git a/ios/Video/RCTVideo.swift b/ios/Video/RCTVideo.swift index 41db2ceca4..4238e36af1 100644 --- a/ios/Video/RCTVideo.swift +++ b/ios/Video/RCTVideo.swift @@ -630,6 +630,9 @@ class RCTVideo: UIView, RCTVideoPlayerViewControllerDelegate, RCTPlayerObserverH } #endif let url = ((playerItem.asset as? AVURLAsset)?.url as? NSURL) + let tracks = _player?.currentItem?.tracks + let videoAsset = tracks?.first + let audtioAsset = tracks?.last url?.m3u_loadAsyncCompletion { model, error in if model != nil { From cab2919ea7fac9256426154f66d04001d36133b6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pawel=20Lozi=C5=84ski?= <pawel.lozinski@miquido.com> Date: Thu, 13 Jun 2024 18:36:03 +0200 Subject: [PATCH 11/37] from: Add proper audio parsing commit: 2c55b87 --- ios/Video/RCTVideo.swift | 118 +++++++++++++++++++++++++++------------ 1 file changed, 82 insertions(+), 36 deletions(-) diff --git a/ios/Video/RCTVideo.swift b/ios/Video/RCTVideo.swift index 4238e36af1..ef4d5d6266 100644 --- a/ios/Video/RCTVideo.swift +++ b/ios/Video/RCTVideo.swift @@ -629,54 +629,76 @@ class RCTVideo: UIView, RCTVideoPlayerViewControllerDelegate, RCTPlayerObserverH playerItem.navigationMarkerGroups = RCTVideoTVUtils.makeNavigationMarkerGroups(chapters) } #endif - let url = ((playerItem.asset as? AVURLAsset)?.url as? NSURL) - let tracks = _player?.currentItem?.tracks - let videoAsset = tracks?.first - let audtioAsset = tracks?.last - - url?.m3u_loadAsyncCompletion { model, error in - if model != nil { - self.onPlayedTracksChange?( - [ - "audioTrack": self.getAudioTrackInfo(model: model), - "textTrack": self._textTracks - ] - ) - } - } return playerItem } - func getAudioTrackInfo(model: M3U8PlaylistModel?) -> [String: Any] { - guard let model else { return .init() } - let group: AVMediaSelectionGroup = _player?.currentItem? - .asset.mediaSelectionGroup(forMediaCharacteristic: .audible) ?? .init() + func getAudioTrackInfo( + model: M3U8PlaylistModel, + masterModel: M3U8PlaylistModel + ) -> [String: Any] { - if group.options.count > 0 { - let currentOption: AVMediaSelectionOption = group.options[0] - - var title: String? - let values = currentOption.commonMetadata.map(\.value) - - if values.count > 0 { - title = values[0] as? String + var streamList: NSArray = .init() + + for i in 0..<masterModel.masterPlaylist.xStreamList.count { + let inf = masterModel.masterPlaylist.xStreamList.xStreamInf(at: i) + if let inf { + streamList.adding(inf) } + } + + if let currentEvent: AVPlayerItemAccessLogEvent = _player?.currentItem?.accessLog()?.events.last { + let predicate: NSPredicate = NSPredicate(format: "%K == %f", "bandwidth", currentEvent.indicatedBitrate) + let filteredArray = streamList.filtered(using: predicate) + let current = filteredArray.last - let language: String = currentOption.extendedLanguageTag ?? "" - - let file: String = model.originalURL.lastPathComponent - + if let current = current as? M3U8ExtXStreamInf { + let mediaList: NSArray = .init() + for i in 0..<masterModel.masterPlaylist.xMediaList.audio().count { + let inf = masterModel.masterPlaylist.xMediaList.audio().xMedia(at: i) + if let inf { + mediaList.adding(inf) + } + } + + let predicate = NSPredicate(format: "SELF.groupId == %@", current.audio) + let currentAudio = mediaList.filtered(using: predicate).last + + if let currentAudio = currentAudio as? M3U8ExtXMedia { + let url: URL = currentAudio.m3u8URL() + let audioModel: M3U8PlaylistModel? = try? .init(url: url) + + if let audioModel { + let audioInfo: M3U8SegmentInfo = audioModel.mainMediaPl.segmentList.segmentInfo(at: 0) + + return [ + "title": currentAudio.name(), + "language": currentAudio.language(), + "codecs": currentAudio.groupId(), + "file": audioInfo.uri + ] + } + + } + } + } + + return .init() + } + + func getVideoTrackInfo(model: M3U8PlaylistModel) -> [String: Any] { + if model.mainMediaPl.segmentList.count > 0 { + let uri = model.mainMediaPl.segmentList.segmentInfo(at: 0).uri return [ - "index": NSNumber(0), - "title": title, - "language": language, - "file": file + "file": uri ] } - return .init() } + func getCurrentMediaData(model: M3U8PlaylistModel) -> M3U8ExtXStreamInf? { + nil + } + // MARK: - Prop setters @objc @@ -1510,11 +1532,35 @@ class RCTVideo: UIView, RCTVideoPlayerViewControllerDelegate, RCTPlayerObserverH } self._videoLoadStarted = false + self.handleMetadataUpdateForTrackChange() self._playerObserver.attachPlayerEventListeners() self.applyModifiers() } } + func handleMetadataUpdateForTrackChange() { + if onPlayedTracksChange != nil { + let string: String = _player?.currentItem?.accessLog()?.events.last?.uri ?? "" + let url = NSURL(string: string) + if let url { + url.m3u_loadAsyncCompletion { model, _ in + let masterURL = (self._player?.currentItem?.asset as? AVURLAsset)?.url + let masterModel: M3U8PlaylistModel? = try? .init(url: masterURL) + + if let model, let masterModel { + self.onPlayedTracksChange?( + [ + "audioTrack": self.getAudioTrackInfo(model: model, masterModel: masterModel), + "textTrack": self._textTracks, + "videoTrack": self.getVideoTrackInfo(model: model) + ] + ) + } + } + } + } + } + func handlePlaybackFailed() { if let player = _player { NowPlayingInfoCenterManager.shared.removePlayer(player: player) From 927d6928b0e3beac841deaf8d49daf6aafe316bb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pawel=20Lozi=C5=84ski?= <pawel.lozinski@miquido.com> Date: Thu, 13 Jun 2024 18:37:38 +0200 Subject: [PATCH 12/37] from: Removed unused method commit: ce29859 --- ios/Video/RCTVideo.swift | 4 ---- 1 file changed, 4 deletions(-) diff --git a/ios/Video/RCTVideo.swift b/ios/Video/RCTVideo.swift index ef4d5d6266..708ceb557c 100644 --- a/ios/Video/RCTVideo.swift +++ b/ios/Video/RCTVideo.swift @@ -695,10 +695,6 @@ class RCTVideo: UIView, RCTVideoPlayerViewControllerDelegate, RCTPlayerObserverH return .init() } - func getCurrentMediaData(model: M3U8PlaylistModel) -> M3U8ExtXStreamInf? { - nil - } - // MARK: - Prop setters @objc From a83755b5c6f0cb84e543c7ad26ed61140c034b29 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pawel=20Lozi=C5=84ski?= <pawel.lozinski@miquido.com> Date: Thu, 13 Jun 2024 18:41:15 +0200 Subject: [PATCH 13/37] from: Fix displaying string uri commit: 320985f --- ios/Video/RCTVideo.swift | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/ios/Video/RCTVideo.swift b/ios/Video/RCTVideo.swift index 708ceb557c..aa3b4a8799 100644 --- a/ios/Video/RCTVideo.swift +++ b/ios/Video/RCTVideo.swift @@ -674,7 +674,7 @@ class RCTVideo: UIView, RCTVideoPlayerViewControllerDelegate, RCTPlayerObserverH "title": currentAudio.name(), "language": currentAudio.language(), "codecs": currentAudio.groupId(), - "file": audioInfo.uri + "file": audioInfo.uri.absoluteString ] } @@ -687,9 +687,9 @@ class RCTVideo: UIView, RCTVideoPlayerViewControllerDelegate, RCTPlayerObserverH func getVideoTrackInfo(model: M3U8PlaylistModel) -> [String: Any] { if model.mainMediaPl.segmentList.count > 0 { - let uri = model.mainMediaPl.segmentList.segmentInfo(at: 0).uri + let stringURL = model.mainMediaPl.segmentList.segmentInfo(at: 0).uri.absoluteString return [ - "file": uri + "file": stringURL ] } return .init() From 4736c317620d4158be3c44cd365b53f3bdcd35a3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pawel=20Lozi=C5=84ski?= <pawel.lozinski@miquido.com> Date: Thu, 13 Jun 2024 19:12:48 +0200 Subject: [PATCH 14/37] from: Feature/xhub 5315 (#14) commit: a6823bb comment: * Add checking values statements * Add video codec * Add library to code itself to fix problem with spaces in uri --- ios/Video/RCTVideo.swift | 63 ++++++++++++++++++++++++++++------------ 1 file changed, 45 insertions(+), 18 deletions(-) diff --git a/ios/Video/RCTVideo.swift b/ios/Video/RCTVideo.swift index aa3b4a8799..a4a91bf95c 100644 --- a/ios/Video/RCTVideo.swift +++ b/ios/Video/RCTVideo.swift @@ -685,11 +685,33 @@ class RCTVideo: UIView, RCTVideoPlayerViewControllerDelegate, RCTPlayerObserverH return .init() } - func getVideoTrackInfo(model: M3U8PlaylistModel) -> [String: Any] { + func getVideoTrackInfo( + model: M3U8PlaylistModel, + masterModel: M3U8PlaylistModel) -> [String: Any] { + if model.mainMediaPl.segmentList.count > 0 { - let stringURL = model.mainMediaPl.segmentList.segmentInfo(at: 0).uri.absoluteString + let uri: URL = model.mainMediaPl.segmentList.segmentInfo(at: 0).uri + if uri == nil { + return .init() + } + + var codecs: String = "" + + for i in 0..<masterModel.masterPlaylist.xStreamList.count { + if let inf = masterModel.masterPlaylist.xStreamList.xStreamInf(at: i) { + let currentPath = (uri.absoluteString as NSString).deletingPathExtension + let infPath = (inf.uri.absoluteString as NSString as NSString).deletingPathExtension + + if currentPath == infPath { + codecs = (inf.codecs as NSArray).componentsJoined(by: ",") + } + } + } + + let stringURL = uri.absoluteString as NSString return [ - "file": stringURL + "file": stringURL, + "codecs": codecs ] } return .init() @@ -1536,21 +1558,26 @@ class RCTVideo: UIView, RCTVideoPlayerViewControllerDelegate, RCTPlayerObserverH func handleMetadataUpdateForTrackChange() { if onPlayedTracksChange != nil { - let string: String = _player?.currentItem?.accessLog()?.events.last?.uri ?? "" - let url = NSURL(string: string) - if let url { - url.m3u_loadAsyncCompletion { model, _ in - let masterURL = (self._player?.currentItem?.asset as? AVURLAsset)?.url - let masterModel: M3U8PlaylistModel? = try? .init(url: masterURL) - - if let model, let masterModel { - self.onPlayedTracksChange?( - [ - "audioTrack": self.getAudioTrackInfo(model: model, masterModel: masterModel), - "textTrack": self._textTracks, - "videoTrack": self.getVideoTrackInfo(model: model) - ] - ) + let urlString: String = _player?.currentItem?.accessLog()?.events.last?.uri ?? "" + let url = NSURL(string: urlString) + let asset: AVAsset? = _player?.currentItem?.asset + let hasVideo: Bool = asset?.tracks(withMediaType: .video).count ?? 0 > 0 + let hasAudtio: Bool = asset?.tracks(withMediaType: .audio).count ?? 0 > 0 + + let masterURL: NSURL? = (_player?.currentItem?.asset as? AVURLAsset)?.url as? NSURL + + masterURL?.m3u_loadAsyncCompletion { masterModel, _ in + if let url { + url.m3u_loadAsyncCompletion { model, _ in + if let model, let masterModel { + self.onPlayedTracksChange?( + [ + "audioTrack": self.getAudioTrackInfo(model: model, masterModel: masterModel), + "textTrack": self._textTracks, + "videoTrack": self.getVideoTrackInfo(model: model, masterModel: masterModel) + ] + ) + } } } } From 06a72309d57f835c16f3ab9ed4c4718cf299ee76 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pawel=20Lozi=C5=84ski?= <pawel.lozinski@miquido.com> Date: Thu, 13 Jun 2024 19:28:45 +0200 Subject: [PATCH 15/37] from: XHUB-5327 - Expose channel (HLS) and Dolby SupplementalProperty (DASH) values from currently played resource to ReactNative (#17) commit: 675207f comment: * Add license text * Add channels --- ios/Video/M3U8Kit/Source/M3U8ExtXByteRange.h | 21 ++++++++++++++++ ios/Video/M3U8Kit/Source/M3U8ExtXByteRange.m | 21 ++++++++++++++++ ios/Video/M3U8Kit/Source/M3U8ExtXKey.h | 21 ++++++++++++++++ ios/Video/M3U8Kit/Source/M3U8ExtXKey.m | 21 ++++++++++++++++ ios/Video/M3U8Kit/Source/M3U8ExtXMedia.h | 22 ++++++++++++++++ ios/Video/M3U8Kit/Source/M3U8ExtXMedia.m | 25 +++++++++++++++++++ ios/Video/M3U8Kit/Source/M3U8ExtXMediaList.h | 21 ++++++++++++++++ ios/Video/M3U8Kit/Source/M3U8ExtXMediaList.m | 21 ++++++++++++++++ ios/Video/M3U8Kit/Source/M3U8ExtXStreamInf.h | 21 ++++++++++++++++ ios/Video/M3U8Kit/Source/M3U8ExtXStreamInf.m | 21 ++++++++++++++++ .../M3U8Kit/Source/M3U8ExtXStreamInfList.h | 21 ++++++++++++++++ .../M3U8Kit/Source/M3U8ExtXStreamInfList.m | 22 +++++++++++++++- ios/Video/M3U8Kit/Source/M3U8LineReader.h | 21 ++++++++++++++++ ios/Video/M3U8Kit/Source/M3U8LineReader.m | 21 ++++++++++++++++ ios/Video/M3U8Kit/Source/M3U8MasterPlaylist.h | 21 ++++++++++++++++ ios/Video/M3U8Kit/Source/M3U8MasterPlaylist.m | 21 ++++++++++++++++ ios/Video/M3U8Kit/Source/M3U8MediaPlaylist.h | 21 ++++++++++++++++ ios/Video/M3U8Kit/Source/M3U8MediaPlaylist.m | 21 ++++++++++++++++ ios/Video/M3U8Kit/Source/M3U8Parser.h | 21 ++++++++++++++++ ios/Video/M3U8Kit/Source/M3U8Parser.modulemap | 22 ++++++++++++++++ ios/Video/M3U8Kit/Source/M3U8PlaylistModel.h | 21 ++++++++++++++++ ios/Video/M3U8Kit/Source/M3U8PlaylistModel.m | 21 ++++++++++++++++ ios/Video/M3U8Kit/Source/M3U8SegmentInfo.h | 21 ++++++++++++++++ ios/Video/M3U8Kit/Source/M3U8SegmentInfo.m | 21 ++++++++++++++++ .../M3U8Kit/Source/M3U8SegmentInfoList.h | 21 ++++++++++++++++ .../M3U8Kit/Source/M3U8SegmentInfoList.m | 21 ++++++++++++++++ .../M3U8Kit/Source/M3U8TagsAndAttributes.h | 24 ++++++++++++++++-- ios/Video/M3U8Kit/Source/NSArray+m3u8.h | 21 ++++++++++++++++ ios/Video/M3U8Kit/Source/NSArray+m3u8.m | 21 ++++++++++++++++ ios/Video/M3U8Kit/Source/NSString+m3u8.h | 21 ++++++++++++++++ ios/Video/M3U8Kit/Source/NSString+m3u8.m | 21 ++++++++++++++++ ios/Video/M3U8Kit/Source/NSURL+m3u8.h | 21 ++++++++++++++++ ios/Video/M3U8Kit/Source/NSURL+m3u8.m | 21 ++++++++++++++++ ios/Video/RCTVideo.swift | 16 ++++-------- 34 files changed, 705 insertions(+), 14 deletions(-) diff --git a/ios/Video/M3U8Kit/Source/M3U8ExtXByteRange.h b/ios/Video/M3U8Kit/Source/M3U8ExtXByteRange.h index 6535b0a168..66479ebc73 100644 --- a/ios/Video/M3U8Kit/Source/M3U8ExtXByteRange.h +++ b/ios/Video/M3U8Kit/Source/M3U8ExtXByteRange.h @@ -5,6 +5,27 @@ // Created by Frank on 2020/10/1. // Copyright © 2020 M3U8Kit. All rights reserved. // +//The MIT License (MIT) +// +//Copyright (c) 2015 Sun Jin <jeansunvf@gmail.com> +// +//Permission is hereby granted, free of charge, to any person obtaining a copy +//of this software and associated documentation files (the "Software"), to deal +//in the Software without restriction, including without limitation the rights +//to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +//copies of the Software, and to permit persons to whom the Software is +//furnished to do so, subject to the following conditions: +// +//The above copyright notice and this permission notice shall be included in all +//copies or substantial portions of the Software. +// +//THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +//IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +//FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +//AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +//LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +//OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +//SOFTWARE. #import <Foundation/Foundation.h> diff --git a/ios/Video/M3U8Kit/Source/M3U8ExtXByteRange.m b/ios/Video/M3U8Kit/Source/M3U8ExtXByteRange.m index 5ad46124cb..4008c80740 100644 --- a/ios/Video/M3U8Kit/Source/M3U8ExtXByteRange.m +++ b/ios/Video/M3U8Kit/Source/M3U8ExtXByteRange.m @@ -5,6 +5,27 @@ // Created by Frank on 2020/10/1. // Copyright © 2020 M3U8Kit. All rights reserved. // +//The MIT License (MIT) +// +//Copyright (c) 2015 Sun Jin <jeansunvf@gmail.com> +// +//Permission is hereby granted, free of charge, to any person obtaining a copy +//of this software and associated documentation files (the "Software"), to deal +//in the Software without restriction, including without limitation the rights +//to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +//copies of the Software, and to permit persons to whom the Software is +//furnished to do so, subject to the following conditions: +// +//The above copyright notice and this permission notice shall be included in all +//copies or substantial portions of the Software. +// +//THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +//IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +//FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +//AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +//LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +//OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +//SOFTWARE. #import "M3U8ExtXByteRange.h" diff --git a/ios/Video/M3U8Kit/Source/M3U8ExtXKey.h b/ios/Video/M3U8Kit/Source/M3U8ExtXKey.h index 6cf67d0aa4..69293e7150 100644 --- a/ios/Video/M3U8Kit/Source/M3U8ExtXKey.h +++ b/ios/Video/M3U8Kit/Source/M3U8ExtXKey.h @@ -5,6 +5,27 @@ // Created by Pierre Perrin on 01/02/2019. // Copyright © 2019 M3U8Kit. All rights reserved. // +//The MIT License (MIT) +// +//Copyright (c) 2015 Sun Jin <jeansunvf@gmail.com> +// +//Permission is hereby granted, free of charge, to any person obtaining a copy +//of this software and associated documentation files (the "Software"), to deal +//in the Software without restriction, including without limitation the rights +//to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +//copies of the Software, and to permit persons to whom the Software is +//furnished to do so, subject to the following conditions: +// +//The above copyright notice and this permission notice shall be included in all +//copies or substantial portions of the Software. +// +//THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +//IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +//FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +//AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +//LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +//OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +//SOFTWARE. #import <Foundation/Foundation.h> @interface M3U8ExtXKey : NSObject diff --git a/ios/Video/M3U8Kit/Source/M3U8ExtXKey.m b/ios/Video/M3U8Kit/Source/M3U8ExtXKey.m index 0d3a4bf018..47a433b041 100644 --- a/ios/Video/M3U8Kit/Source/M3U8ExtXKey.m +++ b/ios/Video/M3U8Kit/Source/M3U8ExtXKey.m @@ -5,6 +5,27 @@ // Created by Pierre Perrin on 01/02/2019. // Copyright © 2019 M3U8Kit. All rights reserved. // +//The MIT License (MIT) +// +//Copyright (c) 2015 Sun Jin <jeansunvf@gmail.com> +// +//Permission is hereby granted, free of charge, to any person obtaining a copy +//of this software and associated documentation files (the "Software"), to deal +//in the Software without restriction, including without limitation the rights +//to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +//copies of the Software, and to permit persons to whom the Software is +//furnished to do so, subject to the following conditions: +// +//The above copyright notice and this permission notice shall be included in all +//copies or substantial portions of the Software. +// +//THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +//IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +//FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +//AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +//LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +//OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +//SOFTWARE. #import "M3U8ExtXKey.h" #import "M3U8TagsAndAttributes.h" diff --git a/ios/Video/M3U8Kit/Source/M3U8ExtXMedia.h b/ios/Video/M3U8Kit/Source/M3U8ExtXMedia.h index eca3d44d6a..f5c13cfede 100644 --- a/ios/Video/M3U8Kit/Source/M3U8ExtXMedia.h +++ b/ios/Video/M3U8Kit/Source/M3U8ExtXMedia.h @@ -5,6 +5,27 @@ // Created by Sun Jin on 3/25/14. // Copyright (c) 2014 Jin Sun. All rights reserved. // +//The MIT License (MIT) +// +//Copyright (c) 2015 Sun Jin <jeansunvf@gmail.com> +// +//Permission is hereby granted, free of charge, to any person obtaining a copy +//of this software and associated documentation files (the "Software"), to deal +//in the Software without restriction, including without limitation the rights +//to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +//copies of the Software, and to permit persons to whom the Software is +//furnished to do so, subject to the following conditions: +// +//The above copyright notice and this permission notice shall be included in all +//copies or substantial portions of the Software. +// +//THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +//IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +//FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +//AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +//LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +//OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +//SOFTWARE. #import <Foundation/Foundation.h> @@ -38,6 +59,7 @@ - (NSString *)type; - (NSURL *)URI; - (NSString *)groupId; +- (NSString *)channels; - (NSString *)language; - (NSString *)assocLanguage; - (NSString *)name; diff --git a/ios/Video/M3U8Kit/Source/M3U8ExtXMedia.m b/ios/Video/M3U8Kit/Source/M3U8ExtXMedia.m index 824a607173..2371b38b72 100644 --- a/ios/Video/M3U8Kit/Source/M3U8ExtXMedia.m +++ b/ios/Video/M3U8Kit/Source/M3U8ExtXMedia.m @@ -5,6 +5,27 @@ // Created by Sun Jin on 3/25/14. // Copyright (c) 2014 Jin Sun. All rights reserved. // +//The MIT License (MIT) +// +//Copyright (c) 2015 Sun Jin <jeansunvf@gmail.com> +// +//Permission is hereby granted, free of charge, to any person obtaining a copy +//of this software and associated documentation files (the "Software"), to deal +//in the Software without restriction, including without limitation the rights +//to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +//copies of the Software, and to permit persons to whom the Software is +//furnished to do so, subject to the following conditions: +// +//The above copyright notice and this permission notice shall be included in all +//copies or substantial portions of the Software. +// +//THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +//IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +//FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +//AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +//LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +//OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +//SOFTWARE. #import "M3U8ExtXMedia.h" #import "M3U8TagsAndAttributes.h" @@ -44,6 +65,10 @@ - (NSString *)groupId { return self.dictionary[M3U8_EXT_X_MEDIA_GROUP_ID]; } +- (NSString *)channels { + return self.dictionary[M3U8_EXT_X_MEDIA_CHANNELS]; +} + - (NSString *)language { return [self.dictionary[M3U8_EXT_X_MEDIA_LANGUAGE] lowercaseString]; } diff --git a/ios/Video/M3U8Kit/Source/M3U8ExtXMediaList.h b/ios/Video/M3U8Kit/Source/M3U8ExtXMediaList.h index b63bcd2c78..8d502f4dd9 100644 --- a/ios/Video/M3U8Kit/Source/M3U8ExtXMediaList.h +++ b/ios/Video/M3U8Kit/Source/M3U8ExtXMediaList.h @@ -5,6 +5,27 @@ // Created by Sun Jin on 3/25/14. // Copyright (c) 2014 Jin Sun. All rights reserved. // +//The MIT License (MIT) +// +//Copyright (c) 2015 Sun Jin <jeansunvf@gmail.com> +// +//Permission is hereby granted, free of charge, to any person obtaining a copy +//of this software and associated documentation files (the "Software"), to deal +//in the Software without restriction, including without limitation the rights +//to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +//copies of the Software, and to permit persons to whom the Software is +//furnished to do so, subject to the following conditions: +// +//The above copyright notice and this permission notice shall be included in all +//copies or substantial portions of the Software. +// +//THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +//IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +//FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +//AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +//LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +//OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +//SOFTWARE. #import <Foundation/Foundation.h> #import "M3U8ExtXMedia.h" diff --git a/ios/Video/M3U8Kit/Source/M3U8ExtXMediaList.m b/ios/Video/M3U8Kit/Source/M3U8ExtXMediaList.m index 0e9f68a74f..be92dcf845 100644 --- a/ios/Video/M3U8Kit/Source/M3U8ExtXMediaList.m +++ b/ios/Video/M3U8Kit/Source/M3U8ExtXMediaList.m @@ -5,6 +5,27 @@ // Created by Sun Jin on 3/25/14. // Copyright (c) 2014 Jin Sun. All rights reserved. // +//The MIT License (MIT) +// +//Copyright (c) 2015 Sun Jin <jeansunvf@gmail.com> +// +//Permission is hereby granted, free of charge, to any person obtaining a copy +//of this software and associated documentation files (the "Software"), to deal +//in the Software without restriction, including without limitation the rights +//to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +//copies of the Software, and to permit persons to whom the Software is +//furnished to do so, subject to the following conditions: +// +//The above copyright notice and this permission notice shall be included in all +//copies or substantial portions of the Software. +// +//THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +//IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +//FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +//AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +//LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +//OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +//SOFTWARE. #import "M3U8ExtXMediaList.h" diff --git a/ios/Video/M3U8Kit/Source/M3U8ExtXStreamInf.h b/ios/Video/M3U8Kit/Source/M3U8ExtXStreamInf.h index 868a0b8424..b37f26d2ec 100644 --- a/ios/Video/M3U8Kit/Source/M3U8ExtXStreamInf.h +++ b/ios/Video/M3U8Kit/Source/M3U8ExtXStreamInf.h @@ -5,6 +5,27 @@ // Created by Jin Sun on 13-4-15. // Copyright (c) 2013年 iLegendSoft. All rights reserved. // +//The MIT License (MIT) +// +//Copyright (c) 2015 Sun Jin <jeansunvf@gmail.com> +// +//Permission is hereby granted, free of charge, to any person obtaining a copy +//of this software and associated documentation files (the "Software"), to deal +//in the Software without restriction, including without limitation the rights +//to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +//copies of the Software, and to permit persons to whom the Software is +//furnished to do so, subject to the following conditions: +// +//The above copyright notice and this permission notice shall be included in all +//copies or substantial portions of the Software. +// +//THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +//IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +//FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +//AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +//LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +//OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +//SOFTWARE. #import <Foundation/Foundation.h> diff --git a/ios/Video/M3U8Kit/Source/M3U8ExtXStreamInf.m b/ios/Video/M3U8Kit/Source/M3U8ExtXStreamInf.m index 9143312754..77d7ad2896 100644 --- a/ios/Video/M3U8Kit/Source/M3U8ExtXStreamInf.m +++ b/ios/Video/M3U8Kit/Source/M3U8ExtXStreamInf.m @@ -5,6 +5,27 @@ // Created by Jin Sun on 13-4-15. // Copyright (c) 2013年 iLegendSoft. All rights reserved. // +//The MIT License (MIT) +// +//Copyright (c) 2015 Sun Jin <jeansunvf@gmail.com> +// +//Permission is hereby granted, free of charge, to any person obtaining a copy +//of this software and associated documentation files (the "Software"), to deal +//in the Software without restriction, including without limitation the rights +//to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +//copies of the Software, and to permit persons to whom the Software is +//furnished to do so, subject to the following conditions: +// +//The above copyright notice and this permission notice shall be included in all +//copies or substantial portions of the Software. +// +//THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +//IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +//FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +//AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +//LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +//OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +//SOFTWARE. #import "M3U8ExtXStreamInf.h" #import "M3U8TagsAndAttributes.h" diff --git a/ios/Video/M3U8Kit/Source/M3U8ExtXStreamInfList.h b/ios/Video/M3U8Kit/Source/M3U8ExtXStreamInfList.h index dc0a48e77a..485c4d0df6 100644 --- a/ios/Video/M3U8Kit/Source/M3U8ExtXStreamInfList.h +++ b/ios/Video/M3U8Kit/Source/M3U8ExtXStreamInfList.h @@ -5,6 +5,27 @@ // Created by Jin Sun on 13-4-15. // Copyright (c) 2013年 iLegendSoft. All rights reserved. // +//The MIT License (MIT) +// +//Copyright (c) 2015 Sun Jin <jeansunvf@gmail.com> +// +//Permission is hereby granted, free of charge, to any person obtaining a copy +//of this software and associated documentation files (the "Software"), to deal +//in the Software without restriction, including without limitation the rights +//to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +//copies of the Software, and to permit persons to whom the Software is +//furnished to do so, subject to the following conditions: +// +//The above copyright notice and this permission notice shall be included in all +//copies or substantial portions of the Software. +// +//THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +//IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +//FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +//AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +//LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +//OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +//SOFTWARE. #import <Foundation/Foundation.h> #import "M3U8ExtXStreamInf.h" diff --git a/ios/Video/M3U8Kit/Source/M3U8ExtXStreamInfList.m b/ios/Video/M3U8Kit/Source/M3U8ExtXStreamInfList.m index 505d904ae9..b5030e1709 100644 --- a/ios/Video/M3U8Kit/Source/M3U8ExtXStreamInfList.m +++ b/ios/Video/M3U8Kit/Source/M3U8ExtXStreamInfList.m @@ -5,7 +5,27 @@ // Created by Jin Sun on 13-4-15. // Copyright (c) 2013年 iLegendSoft. All rights reserved. // - +//The MIT License (MIT) +// +//Copyright (c) 2015 Sun Jin <jeansunvf@gmail.com> +// +//Permission is hereby granted, free of charge, to any person obtaining a copy +//of this software and associated documentation files (the "Software"), to deal +//in the Software without restriction, including without limitation the rights +//to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +//copies of the Software, and to permit persons to whom the Software is +//furnished to do so, subject to the following conditions: +// +//The above copyright notice and this permission notice shall be included in all +//copies or substantial portions of the Software. +// +//THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +//IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +//FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +//AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +//LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +//OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +//SOFTWARE. #import "M3U8ExtXStreamInfList.h" @interface M3U8ExtXStreamInfList () diff --git a/ios/Video/M3U8Kit/Source/M3U8LineReader.h b/ios/Video/M3U8Kit/Source/M3U8LineReader.h index a000d01cdc..419fe0ed9a 100644 --- a/ios/Video/M3U8Kit/Source/M3U8LineReader.h +++ b/ios/Video/M3U8Kit/Source/M3U8LineReader.h @@ -4,6 +4,27 @@ // // Created by Noam Tamim on 22/03/2018. // +//The MIT License (MIT) +// +//Copyright (c) 2015 Sun Jin <jeansunvf@gmail.com> +// +//Permission is hereby granted, free of charge, to any person obtaining a copy +//of this software and associated documentation files (the "Software"), to deal +//in the Software without restriction, including without limitation the rights +//to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +//copies of the Software, and to permit persons to whom the Software is +//furnished to do so, subject to the following conditions: +// +//The above copyright notice and this permission notice shall be included in all +//copies or substantial portions of the Software. +// +//THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +//IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +//FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +//AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +//LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +//OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +//SOFTWARE. #import <Foundation/Foundation.h> diff --git a/ios/Video/M3U8Kit/Source/M3U8LineReader.m b/ios/Video/M3U8Kit/Source/M3U8LineReader.m index 9cc49f465c..0647fbd21d 100644 --- a/ios/Video/M3U8Kit/Source/M3U8LineReader.m +++ b/ios/Video/M3U8Kit/Source/M3U8LineReader.m @@ -4,6 +4,27 @@ // // Created by Noam Tamim on 22/03/2018. // +//The MIT License (MIT) +// +//Copyright (c) 2015 Sun Jin <jeansunvf@gmail.com> +// +//Permission is hereby granted, free of charge, to any person obtaining a copy +//of this software and associated documentation files (the "Software"), to deal +//in the Software without restriction, including without limitation the rights +//to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +//copies of the Software, and to permit persons to whom the Software is +//furnished to do so, subject to the following conditions: +// +//The above copyright notice and this permission notice shall be included in all +//copies or substantial portions of the Software. +// +//THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +//IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +//FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +//AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +//LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +//OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +//SOFTWARE. #import "M3U8LineReader.h" diff --git a/ios/Video/M3U8Kit/Source/M3U8MasterPlaylist.h b/ios/Video/M3U8Kit/Source/M3U8MasterPlaylist.h index 5d9059d360..2445a23ec2 100644 --- a/ios/Video/M3U8Kit/Source/M3U8MasterPlaylist.h +++ b/ios/Video/M3U8Kit/Source/M3U8MasterPlaylist.h @@ -5,6 +5,27 @@ // Created by Sun Jin on 3/25/14. // Copyright (c) 2014 Jin Sun. All rights reserved. // +//The MIT License (MIT) +// +//Copyright (c) 2015 Sun Jin <jeansunvf@gmail.com> +// +//Permission is hereby granted, free of charge, to any person obtaining a copy +//of this software and associated documentation files (the "Software"), to deal +//in the Software without restriction, including without limitation the rights +//to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +//copies of the Software, and to permit persons to whom the Software is +//furnished to do so, subject to the following conditions: +// +//The above copyright notice and this permission notice shall be included in all +//copies or substantial portions of the Software. +// +//THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +//IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +//FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +//AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +//LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +//OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +//SOFTWARE. #import <Foundation/Foundation.h> #import "M3U8ExtXStreamInfList.h" diff --git a/ios/Video/M3U8Kit/Source/M3U8MasterPlaylist.m b/ios/Video/M3U8Kit/Source/M3U8MasterPlaylist.m index 9deafb03ed..a5634970fb 100644 --- a/ios/Video/M3U8Kit/Source/M3U8MasterPlaylist.m +++ b/ios/Video/M3U8Kit/Source/M3U8MasterPlaylist.m @@ -5,6 +5,27 @@ // Created by Sun Jin on 3/25/14. // Copyright (c) 2014 Jin Sun. All rights reserved. // +//The MIT License (MIT) +// +//Copyright (c) 2015 Sun Jin <jeansunvf@gmail.com> +// +//Permission is hereby granted, free of charge, to any person obtaining a copy +//of this software and associated documentation files (the "Software"), to deal +//in the Software without restriction, including without limitation the rights +//to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +//copies of the Software, and to permit persons to whom the Software is +//furnished to do so, subject to the following conditions: +// +//The above copyright notice and this permission notice shall be included in all +//copies or substantial portions of the Software. +// +//THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +//IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +//FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +//AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +//LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +//OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +//SOFTWARE. #import "M3U8MasterPlaylist.h" #import "NSString+m3u8.h" diff --git a/ios/Video/M3U8Kit/Source/M3U8MediaPlaylist.h b/ios/Video/M3U8Kit/Source/M3U8MediaPlaylist.h index c22a3aeca5..3b07ed0a7b 100644 --- a/ios/Video/M3U8Kit/Source/M3U8MediaPlaylist.h +++ b/ios/Video/M3U8Kit/Source/M3U8MediaPlaylist.h @@ -5,6 +5,27 @@ // Created by Sun Jin on 3/26/14. // Copyright (c) 2014 Jin Sun. All rights reserved. // +//The MIT License (MIT) +// +//Copyright (c) 2015 Sun Jin <jeansunvf@gmail.com> +// +//Permission is hereby granted, free of charge, to any person obtaining a copy +//of this software and associated documentation files (the "Software"), to deal +//in the Software without restriction, including without limitation the rights +//to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +//copies of the Software, and to permit persons to whom the Software is +//furnished to do so, subject to the following conditions: +// +//The above copyright notice and this permission notice shall be included in all +//copies or substantial portions of the Software. +// +//THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +//IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +//FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +//AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +//LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +//OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +//SOFTWARE. #import <Foundation/Foundation.h> #import "M3U8SegmentInfoList.h" diff --git a/ios/Video/M3U8Kit/Source/M3U8MediaPlaylist.m b/ios/Video/M3U8Kit/Source/M3U8MediaPlaylist.m index 78e1cc6fe3..9fe9e8a74b 100644 --- a/ios/Video/M3U8Kit/Source/M3U8MediaPlaylist.m +++ b/ios/Video/M3U8Kit/Source/M3U8MediaPlaylist.m @@ -5,6 +5,27 @@ // Created by Sun Jin on 3/26/14. // Copyright (c) 2014 Jin Sun. All rights reserved. // +//The MIT License (MIT) +// +//Copyright (c) 2015 Sun Jin <jeansunvf@gmail.com> +// +//Permission is hereby granted, free of charge, to any person obtaining a copy +//of this software and associated documentation files (the "Software"), to deal +//in the Software without restriction, including without limitation the rights +//to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +//copies of the Software, and to permit persons to whom the Software is +//furnished to do so, subject to the following conditions: +// +//The above copyright notice and this permission notice shall be included in all +//copies or substantial portions of the Software. +// +//THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +//IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +//FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +//AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +//LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +//OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +//SOFTWARE. #import "M3U8MediaPlaylist.h" #import "NSString+m3u8.h" diff --git a/ios/Video/M3U8Kit/Source/M3U8Parser.h b/ios/Video/M3U8Kit/Source/M3U8Parser.h index d35d760cb7..4b23d09859 100644 --- a/ios/Video/M3U8Kit/Source/M3U8Parser.h +++ b/ios/Video/M3U8Kit/Source/M3U8Parser.h @@ -5,6 +5,27 @@ // Created by Frank on 20-4-16. // Copyright (c) 2020年 M3U8Kit. All rights reserved. // +//The MIT License (MIT) +// +//Copyright (c) 2015 Sun Jin <jeansunvf@gmail.com> +// +//Permission is hereby granted, free of charge, to any person obtaining a copy +//of this software and associated documentation files (the "Software"), to deal +//in the Software without restriction, including without limitation the rights +//to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +//copies of the Software, and to permit persons to whom the Software is +//furnished to do so, subject to the following conditions: +// +//The above copyright notice and this permission notice shall be included in all +//copies or substantial portions of the Software. +// +//THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +//IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +//FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +//AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +//LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +//OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +//SOFTWARE. #import "M3U8ExtXStreamInf.h" #import "M3U8ExtXStreamInfList.h" diff --git a/ios/Video/M3U8Kit/Source/M3U8Parser.modulemap b/ios/Video/M3U8Kit/Source/M3U8Parser.modulemap index fd5b9dee2e..d3e6f04322 100644 --- a/ios/Video/M3U8Kit/Source/M3U8Parser.modulemap +++ b/ios/Video/M3U8Kit/Source/M3U8Parser.modulemap @@ -1,3 +1,25 @@ +//The MIT License (MIT) +// +//Copyright (c) 2015 Sun Jin <jeansunvf@gmail.com> +// +//Permission is hereby granted, free of charge, to any person obtaining a copy +//of this software and associated documentation files (the "Software"), to deal +//in the Software without restriction, including without limitation the rights +//to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +//copies of the Software, and to permit persons to whom the Software is +//furnished to do so, subject to the following conditions: +// +//The above copyright notice and this permission notice shall be included in all +//copies or substantial portions of the Software. +// +//THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +//IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +//FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +//AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +//LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +//OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +//SOFTWARE. + framework module M3U8Parser { umbrella header "M3U8Parser.h" diff --git a/ios/Video/M3U8Kit/Source/M3U8PlaylistModel.h b/ios/Video/M3U8Kit/Source/M3U8PlaylistModel.h index 062a498184..2e371c1166 100644 --- a/ios/Video/M3U8Kit/Source/M3U8PlaylistModel.h +++ b/ios/Video/M3U8Kit/Source/M3U8PlaylistModel.h @@ -5,6 +5,27 @@ // Created by Oneday on 13-1-11. // Copyright (c) 2013年 0day. All rights reserved. // +//The MIT License (MIT) +// +//Copyright (c) 2015 Sun Jin <jeansunvf@gmail.com> +// +//Permission is hereby granted, free of charge, to any person obtaining a copy +//of this software and associated documentation files (the "Software"), to deal +//in the Software without restriction, including without limitation the rights +//to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +//copies of the Software, and to permit persons to whom the Software is +//furnished to do so, subject to the following conditions: +// +//The above copyright notice and this permission notice shall be included in all +//copies or substantial portions of the Software. +// +//THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +//IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +//FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +//AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +//LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +//OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +//SOFTWARE. #import <Foundation/Foundation.h> #import "M3U8MasterPlaylist.h" diff --git a/ios/Video/M3U8Kit/Source/M3U8PlaylistModel.m b/ios/Video/M3U8Kit/Source/M3U8PlaylistModel.m index 75c6f1de6f..c6c6a0bf2e 100644 --- a/ios/Video/M3U8Kit/Source/M3U8PlaylistModel.m +++ b/ios/Video/M3U8Kit/Source/M3U8PlaylistModel.m @@ -5,6 +5,27 @@ // Created by Oneday on 13-1-11. // Copyright (c) 2013年 0day. All rights reserved. // +//The MIT License (MIT) +// +//Copyright (c) 2015 Sun Jin <jeansunvf@gmail.com> +// +//Permission is hereby granted, free of charge, to any person obtaining a copy +//of this software and associated documentation files (the "Software"), to deal +//in the Software without restriction, including without limitation the rights +//to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +//copies of the Software, and to permit persons to whom the Software is +//furnished to do so, subject to the following conditions: +// +//The above copyright notice and this permission notice shall be included in all +//copies or substantial portions of the Software. +// +//THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +//IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +//FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +//AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +//LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +//OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +//SOFTWARE. #import "M3U8PlaylistModel.h" #import "NSString+m3u8.h" diff --git a/ios/Video/M3U8Kit/Source/M3U8SegmentInfo.h b/ios/Video/M3U8Kit/Source/M3U8SegmentInfo.h index 10c0db967c..054f696be8 100644 --- a/ios/Video/M3U8Kit/Source/M3U8SegmentInfo.h +++ b/ios/Video/M3U8Kit/Source/M3U8SegmentInfo.h @@ -5,6 +5,27 @@ // Created by Oneday on 13-1-11. // Copyright (c) 2013年 0day. All rights reserved. // +//The MIT License (MIT) +// +//Copyright (c) 2015 Sun Jin <jeansunvf@gmail.com> +// +//Permission is hereby granted, free of charge, to any person obtaining a copy +//of this software and associated documentation files (the "Software"), to deal +//in the Software without restriction, including without limitation the rights +//to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +//copies of the Software, and to permit persons to whom the Software is +//furnished to do so, subject to the following conditions: +// +//The above copyright notice and this permission notice shall be included in all +//copies or substantial portions of the Software. +// +//THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +//IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +//FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +//AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +//LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +//OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +//SOFTWARE. #import <Foundation/Foundation.h> diff --git a/ios/Video/M3U8Kit/Source/M3U8SegmentInfo.m b/ios/Video/M3U8Kit/Source/M3U8SegmentInfo.m index 55cbd015ec..0f2a8fd084 100644 --- a/ios/Video/M3U8Kit/Source/M3U8SegmentInfo.m +++ b/ios/Video/M3U8Kit/Source/M3U8SegmentInfo.m @@ -5,6 +5,27 @@ // Created by Oneday on 13-1-11. // Copyright (c) 2013年 0day. All rights reserved. // +//The MIT License (MIT) +// +//Copyright (c) 2015 Sun Jin <jeansunvf@gmail.com> +// +//Permission is hereby granted, free of charge, to any person obtaining a copy +//of this software and associated documentation files (the "Software"), to deal +//in the Software without restriction, including without limitation the rights +//to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +//copies of the Software, and to permit persons to whom the Software is +//furnished to do so, subject to the following conditions: +// +//The above copyright notice and this permission notice shall be included in all +//copies or substantial portions of the Software. +// +//THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +//IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +//FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +//AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +//LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +//OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +//SOFTWARE. #import "M3U8SegmentInfo.h" #import "M3U8TagsAndAttributes.h" diff --git a/ios/Video/M3U8Kit/Source/M3U8SegmentInfoList.h b/ios/Video/M3U8Kit/Source/M3U8SegmentInfoList.h index a8bdc0751b..5f76d3bc25 100644 --- a/ios/Video/M3U8Kit/Source/M3U8SegmentInfoList.h +++ b/ios/Video/M3U8Kit/Source/M3U8SegmentInfoList.h @@ -5,6 +5,27 @@ // Created by Oneday on 13-1-11. // Copyright (c) 2013年 0day. All rights reserved. // +//The MIT License (MIT) +// +//Copyright (c) 2015 Sun Jin <jeansunvf@gmail.com> +// +//Permission is hereby granted, free of charge, to any person obtaining a copy +//of this software and associated documentation files (the "Software"), to deal +//in the Software without restriction, including without limitation the rights +//to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +//copies of the Software, and to permit persons to whom the Software is +//furnished to do so, subject to the following conditions: +// +//The above copyright notice and this permission notice shall be included in all +//copies or substantial portions of the Software. +// +//THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +//IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +//FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +//AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +//LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +//OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +//SOFTWARE. #import <Foundation/Foundation.h> #import "M3U8SegmentInfo.h" diff --git a/ios/Video/M3U8Kit/Source/M3U8SegmentInfoList.m b/ios/Video/M3U8Kit/Source/M3U8SegmentInfoList.m index b30b45f280..6e05967831 100644 --- a/ios/Video/M3U8Kit/Source/M3U8SegmentInfoList.m +++ b/ios/Video/M3U8Kit/Source/M3U8SegmentInfoList.m @@ -5,6 +5,27 @@ // Created by Oneday on 13-1-11. // Copyright (c) 2013年 0day. All rights reserved. // +//The MIT License (MIT) +// +//Copyright (c) 2015 Sun Jin <jeansunvf@gmail.com> +// +//Permission is hereby granted, free of charge, to any person obtaining a copy +//of this software and associated documentation files (the "Software"), to deal +//in the Software without restriction, including without limitation the rights +//to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +//copies of the Software, and to permit persons to whom the Software is +//furnished to do so, subject to the following conditions: +// +//The above copyright notice and this permission notice shall be included in all +//copies or substantial portions of the Software. +// +//THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +//IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +//FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +//AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +//LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +//OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +//SOFTWARE. #import "M3U8SegmentInfoList.h" diff --git a/ios/Video/M3U8Kit/Source/M3U8TagsAndAttributes.h b/ios/Video/M3U8Kit/Source/M3U8TagsAndAttributes.h index 75117669d3..fba1dc69db 100644 --- a/ios/Video/M3U8Kit/Source/M3U8TagsAndAttributes.h +++ b/ios/Video/M3U8Kit/Source/M3U8TagsAndAttributes.h @@ -5,7 +5,27 @@ // Created by Sun Jin on 3/24/14. // Copyright (c) 2014 Jin Sun. All rights reserved. // - +//The MIT License (MIT) +// +//Copyright (c) 2015 Sun Jin <jeansunvf@gmail.com> +// +//Permission is hereby granted, free of charge, to any person obtaining a copy +//of this software and associated documentation files (the "Software"), to deal +//in the Software without restriction, including without limitation the rights +//to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +//copies of the Software, and to permit persons to whom the Software is +//furnished to do so, subject to the following conditions: +// +//The above copyright notice and this permission notice shall be included in all +//copies or substantial portions of the Software. +// +//THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +//IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +//FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +//AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +//LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +//OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +//SOFTWARE. // M3U8 Tags & Attributes definded in Draft Pantos Http Live Streaming 12 http://tools.ietf.org/html/draft-pantos-http-live-streaming-12 @@ -190,7 +210,7 @@ #define M3U8_EXT_X_MEDIA_INSTREAM_ID @"INSTREAM-ID" // The value is a quoted-string that specifies a rendition within the segments in the Media Playlist. #define M3U8_EXT_X_MEDIA_CHARACTERISTICS @"CHARACTERISTICS" // The value is a quoted-string containing one or more Uniform Type Identifiers [UTI] separated by comma (,) characters. #define M3U8_EXT_X_MEDIA_BANDWIDTH @"BANDWIDTH" - +#define M3U8_EXT_X_MEDIA_CHANNELS @"CHANNELS" /// EXT-X-STREAM-INF /** diff --git a/ios/Video/M3U8Kit/Source/NSArray+m3u8.h b/ios/Video/M3U8Kit/Source/NSArray+m3u8.h index d61cbe3730..214abff480 100644 --- a/ios/Video/M3U8Kit/Source/NSArray+m3u8.h +++ b/ios/Video/M3U8Kit/Source/NSArray+m3u8.h @@ -5,6 +5,27 @@ // Created by Frank on 2022/7/12. // Copyright © 2022 M3U8Kit. All rights reserved. // +//The MIT License (MIT) +// +//Copyright (c) 2015 Sun Jin <jeansunvf@gmail.com> +// +//Permission is hereby granted, free of charge, to any person obtaining a copy +//of this software and associated documentation files (the "Software"), to deal +//in the Software without restriction, including without limitation the rights +//to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +//copies of the Software, and to permit persons to whom the Software is +//furnished to do so, subject to the following conditions: +// +//The above copyright notice and this permission notice shall be included in all +//copies or substantial portions of the Software. +// +//THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +//IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +//FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +//AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +//LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +//OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +//SOFTWARE. #import <Foundation/Foundation.h> diff --git a/ios/Video/M3U8Kit/Source/NSArray+m3u8.m b/ios/Video/M3U8Kit/Source/NSArray+m3u8.m index 27041225d0..724c939427 100644 --- a/ios/Video/M3U8Kit/Source/NSArray+m3u8.m +++ b/ios/Video/M3U8Kit/Source/NSArray+m3u8.m @@ -5,6 +5,27 @@ // Created by Frank on 2022/7/12. // Copyright © 2022 M3U8Kit. All rights reserved. // +//The MIT License (MIT) +// +//Copyright (c) 2015 Sun Jin <jeansunvf@gmail.com> +// +//Permission is hereby granted, free of charge, to any person obtaining a copy +//of this software and associated documentation files (the "Software"), to deal +//in the Software without restriction, including without limitation the rights +//to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +//copies of the Software, and to permit persons to whom the Software is +//furnished to do so, subject to the following conditions: +// +//The above copyright notice and this permission notice shall be included in all +//copies or substantial portions of the Software. +// +//THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +//IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +//FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +//AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +//LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +//OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +//SOFTWARE. #import "NSArray+m3u8.h" #import "NSString+m3u8.h" diff --git a/ios/Video/M3U8Kit/Source/NSString+m3u8.h b/ios/Video/M3U8Kit/Source/NSString+m3u8.h index 519423f05c..e0cf760073 100644 --- a/ios/Video/M3U8Kit/Source/NSString+m3u8.h +++ b/ios/Video/M3U8Kit/Source/NSString+m3u8.h @@ -5,6 +5,27 @@ // Created by Oneday on 13-1-11. // Copyright (c) 2013年 0day. All rights reserved. // +//The MIT License (MIT) +// +//Copyright (c) 2015 Sun Jin <jeansunvf@gmail.com> +// +//Permission is hereby granted, free of charge, to any person obtaining a copy +//of this software and associated documentation files (the "Software"), to deal +//in the Software without restriction, including without limitation the rights +//to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +//copies of the Software, and to permit persons to whom the Software is +//furnished to do so, subject to the following conditions: +// +//The above copyright notice and this permission notice shall be included in all +//copies or substantial portions of the Software. +// +//THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +//IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +//FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +//AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +//LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +//OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +//SOFTWARE. #import <Foundation/Foundation.h> diff --git a/ios/Video/M3U8Kit/Source/NSString+m3u8.m b/ios/Video/M3U8Kit/Source/NSString+m3u8.m index b41f917c02..01d2c4e57c 100644 --- a/ios/Video/M3U8Kit/Source/NSString+m3u8.m +++ b/ios/Video/M3U8Kit/Source/NSString+m3u8.m @@ -5,6 +5,27 @@ // Created by Oneday on 13-1-11. // Copyright (c) 2013年 0day. All rights reserved. // +//The MIT License (MIT) +// +//Copyright (c) 2015 Sun Jin <jeansunvf@gmail.com> +// +//Permission is hereby granted, free of charge, to any person obtaining a copy +//of this software and associated documentation files (the "Software"), to deal +//in the Software without restriction, including without limitation the rights +//to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +//copies of the Software, and to permit persons to whom the Software is +//furnished to do so, subject to the following conditions: +// +//The above copyright notice and this permission notice shall be included in all +//copies or substantial portions of the Software. +// +//THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +//IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +//FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +//AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +//LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +//OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +//SOFTWARE. #import "NSString+m3u8.h" #import "M3U8SegmentInfo.h" diff --git a/ios/Video/M3U8Kit/Source/NSURL+m3u8.h b/ios/Video/M3U8Kit/Source/NSURL+m3u8.h index 57c6d5a041..a489059dcf 100644 --- a/ios/Video/M3U8Kit/Source/NSURL+m3u8.h +++ b/ios/Video/M3U8Kit/Source/NSURL+m3u8.h @@ -4,6 +4,27 @@ // // Created by Frank on 16/06/2017. // +//The MIT License (MIT) +// +//Copyright (c) 2015 Sun Jin <jeansunvf@gmail.com> +// +//Permission is hereby granted, free of charge, to any person obtaining a copy +//of this software and associated documentation files (the "Software"), to deal +//in the Software without restriction, including without limitation the rights +//to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +//copies of the Software, and to permit persons to whom the Software is +//furnished to do so, subject to the following conditions: +// +//The above copyright notice and this permission notice shall be included in all +//copies or substantial portions of the Software. +// +//THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +//IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +//FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +//AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +//LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +//OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +//SOFTWARE. #import <Foundation/Foundation.h> diff --git a/ios/Video/M3U8Kit/Source/NSURL+m3u8.m b/ios/Video/M3U8Kit/Source/NSURL+m3u8.m index d4f0b60d94..af9400ca2f 100644 --- a/ios/Video/M3U8Kit/Source/NSURL+m3u8.m +++ b/ios/Video/M3U8Kit/Source/NSURL+m3u8.m @@ -4,6 +4,27 @@ // // Created by Frank on 16/06/2017. // +//The MIT License (MIT) +// +//Copyright (c) 2015 Sun Jin <jeansunvf@gmail.com> +// +//Permission is hereby granted, free of charge, to any person obtaining a copy +//of this software and associated documentation files (the "Software"), to deal +//in the Software without restriction, including without limitation the rights +//to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +//copies of the Software, and to permit persons to whom the Software is +//furnished to do so, subject to the following conditions: +// +//The above copyright notice and this permission notice shall be included in all +//copies or substantial portions of the Software. +// +//THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +//IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +//FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +//AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +//LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +//OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +//SOFTWARE. #import "NSURL+m3u8.h" #import "M3U8PlaylistModel.h" diff --git a/ios/Video/RCTVideo.swift b/ios/Video/RCTVideo.swift index a4a91bf95c..4290d1d5cd 100644 --- a/ios/Video/RCTVideo.swift +++ b/ios/Video/RCTVideo.swift @@ -674,7 +674,8 @@ class RCTVideo: UIView, RCTVideoPlayerViewControllerDelegate, RCTPlayerObserverH "title": currentAudio.name(), "language": currentAudio.language(), "codecs": currentAudio.groupId(), - "file": audioInfo.uri.absoluteString + "file": audioInfo.uri.absoluteString, + "channels": currentAudio.channels ] } @@ -697,14 +698,9 @@ class RCTVideo: UIView, RCTVideoPlayerViewControllerDelegate, RCTPlayerObserverH var codecs: String = "" - for i in 0..<masterModel.masterPlaylist.xStreamList.count { - if let inf = masterModel.masterPlaylist.xStreamList.xStreamInf(at: i) { - let currentPath = (uri.absoluteString as NSString).deletingPathExtension - let infPath = (inf.uri.absoluteString as NSString as NSString).deletingPathExtension - - if currentPath == infPath { - codecs = (inf.codecs as NSArray).componentsJoined(by: ",") - } + if masterModel.masterPlaylist.xStreamList.count > 0 { + if let inf = masterModel.masterPlaylist.xStreamList.xStreamInf(at: 0) { + codecs = (inf.codecs as NSArray).componentsJoined(by: ",") } } @@ -1561,8 +1557,6 @@ class RCTVideo: UIView, RCTVideoPlayerViewControllerDelegate, RCTPlayerObserverH let urlString: String = _player?.currentItem?.accessLog()?.events.last?.uri ?? "" let url = NSURL(string: urlString) let asset: AVAsset? = _player?.currentItem?.asset - let hasVideo: Bool = asset?.tracks(withMediaType: .video).count ?? 0 > 0 - let hasAudtio: Bool = asset?.tracks(withMediaType: .audio).count ?? 0 > 0 let masterURL: NSURL? = (_player?.currentItem?.asset as? AVURLAsset)?.url as? NSURL From 4174ef4299baa5cb5ae9dafdf239a2df9e022a2b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pawel=20Lozi=C5=84ski?= <pawel.lozinski@miquido.com> Date: Thu, 13 Jun 2024 22:29:23 +0200 Subject: [PATCH 16/37] sh scripts/swift-format.sh --- ios/Video/RCTVideo.swift | 914 +++++++++++++++++++-------------------- 1 file changed, 455 insertions(+), 459 deletions(-) diff --git a/ios/Video/RCTVideo.swift b/ios/Video/RCTVideo.swift index 4290d1d5cd..15b63b8835 100644 --- a/ios/Video/RCTVideo.swift +++ b/ios/Video/RCTVideo.swift @@ -9,46 +9,45 @@ import React // MARK: - RCTVideo class RCTVideo: UIView, RCTVideoPlayerViewControllerDelegate, RCTPlayerObserverHandler { - struct VideoState: OptionSet { let rawValue: UInt - + static let unknown = VideoState(rawValue: 0) static let loaded = VideoState(rawValue: 1 << 0) static let ready = VideoState(rawValue: 1 << 1) } - + private var _player: AVPlayer? private var _playerItem: AVPlayerItem? private var _source: VideoSource? private var _playerLayer: AVPlayerLayer? private var _chapters: [Chapter]? - + private var _playerViewController: RCTVideoPlayerViewController? private var _videoURL: NSURL? - + /* DRM */ private var _drm: DRMParams? - + private var _localSourceEncryptionKeyScheme: String? - + /* Required to publish events */ private var _eventDispatcher: RCTEventDispatcher? private var _videoLoadStarted = false - + private var _pendingSeek = false private var _pendingSeekTime: Float = 0.0 private var _lastSeekTime: Float = 0.0 - + /* For sending videoProgress events */ private var _controls = false - + /* Keep track of any modifiers, need to be applied after each play */ private var _audioOutput: String = "speaker" private var _volume: Float = 1.0 private var _rate: Float = 1.0 private var _maxBitRate: Float? - + private var _automaticallyWaitsToMinimizeStalling = true private var _muted = false private var _paused = false @@ -82,45 +81,45 @@ class RCTVideo: UIView, RCTVideoPlayerViewControllerDelegate, RCTPlayerObserverH private var _masterPendingPlayRequest = false private var _pictureInPictureEnabled = false { didSet { -#if os(iOS) - if _pictureInPictureEnabled { - initPictureinPicture() - _playerViewController?.allowsPictureInPicturePlayback = true - } else { - _pip?.deinitPipController() - _playerViewController?.allowsPictureInPicturePlayback = false - } -#endif + #if os(iOS) + if _pictureInPictureEnabled { + initPictureinPicture() + _playerViewController?.allowsPictureInPicturePlayback = true + } else { + _pip?.deinitPipController() + _playerViewController?.allowsPictureInPicturePlayback = false + } + #endif } } - + private var _isBuffering = false { didSet { onVideoBuffer?(["isBuffering": _isBuffering, "target": reactTag as Any]) } } - + /* IMA Ads */ private var _adTagUrl: String? -#if USE_GOOGLE_IMA - private var _imaAdsManager: RCTIMAAdsManager! - /* Playhead used by the SDK to track content video progress and insert mid-rolls. */ - private var _contentPlayhead: IMAAVPlayerContentPlayhead? -#endif + #if USE_GOOGLE_IMA + private var _imaAdsManager: RCTIMAAdsManager! + /* Playhead used by the SDK to track content video progress and insert mid-rolls. */ + private var _contentPlayhead: IMAAVPlayerContentPlayhead? + #endif private var _didRequestAds = false private var _adPlaying = false - + private var _resouceLoaderDelegate: RCTResourceLoaderDelegate? private var _playerObserver: RCTPlayerObserver = .init() - -#if USE_VIDEO_CACHING - private let _videoCache: RCTVideoCachingHandler = .init() -#endif - -#if os(iOS) - private var _pip: RCTPictureInPicture? -#endif - + + #if USE_VIDEO_CACHING + private let _videoCache: RCTVideoCachingHandler = .init() + #endif + + #if os(iOS) + private var _pip: RCTPictureInPicture? + #endif + // Events @objc var onVideoLoadStart: RCTDirectEventBlock? @objc var onVideoLoad: RCTDirectEventBlock? @@ -152,96 +151,96 @@ class RCTVideo: UIView, RCTVideoPlayerViewControllerDelegate, RCTPlayerObserverH @objc var onTextTracks: RCTDirectEventBlock? @objc var onAudioTracks: RCTDirectEventBlock? @objc var onTextTrackDataChanged: RCTDirectEventBlock? - + @objc func _onPictureInPictureEnter() { onPictureInPictureStatusChanged?(["isActive": NSNumber(value: true)]) } - + @objc func _onPictureInPictureExit() { onPictureInPictureStatusChanged?(["isActive": NSNumber(value: false)]) } - + func handlePictureInPictureEnter() { onPictureInPictureStatusChanged?(["isActive": NSNumber(value: true)]) } - + func handlePictureInPictureExit() { onPictureInPictureStatusChanged?(["isActive": NSNumber(value: false)]) } - + func handleRestoreUserInterfaceForPictureInPictureStop() { onRestoreUserInterfaceForPictureInPictureStop?([:]) } - + func isPipEnabled() -> Bool { return _pictureInPictureEnabled } - + func initPictureinPicture() { -#if os(iOS) - _pip = RCTPictureInPicture({ [weak self] in - self?._onPictureInPictureEnter() - }, { [weak self] in - self?._onPictureInPictureExit() - }, { [weak self] in - self?.onRestoreUserInterfaceForPictureInPictureStop?([:]) - }) - - if _playerLayer != nil && !_controls { - _pip?.setupPipController(_playerLayer) - } -#else - DebugLog("Picture in Picture is not supported on this platform") -#endif + #if os(iOS) + _pip = RCTPictureInPicture({ [weak self] in + self?._onPictureInPictureEnter() + }, { [weak self] in + self?._onPictureInPictureExit() + }, { [weak self] in + self?.onRestoreUserInterfaceForPictureInPictureStop?([:]) + }) + + if _playerLayer != nil && !_controls { + _pip?.setupPipController(_playerLayer) + } + #else + DebugLog("Picture in Picture is not supported on this platform") + #endif } - + init(eventDispatcher: RCTEventDispatcher!) { super.init(frame: CGRect(x: 0, y: 0, width: 100, height: 100)) -#if USE_GOOGLE_IMA - _imaAdsManager = RCTIMAAdsManager(video: self, pipEnabled: isPipEnabled) -#endif - + #if USE_GOOGLE_IMA + _imaAdsManager = RCTIMAAdsManager(video: self, pipEnabled: isPipEnabled) + #endif + _eventDispatcher = eventDispatcher - -#if os(iOS) - if _pictureInPictureEnabled { - initPictureinPicture() - _playerViewController?.allowsPictureInPicturePlayback = true - } else { - _playerViewController?.allowsPictureInPicturePlayback = false - } -#endif - + + #if os(iOS) + if _pictureInPictureEnabled { + initPictureinPicture() + _playerViewController?.allowsPictureInPicturePlayback = true + } else { + _playerViewController?.allowsPictureInPicturePlayback = false + } + #endif + NotificationCenter.default.addObserver( self, selector: #selector(applicationWillResignActive(notification:)), name: UIApplication.willResignActiveNotification, object: nil ) - + NotificationCenter.default.addObserver( self, selector: #selector(applicationDidBecomeActive(notification:)), name: UIApplication.didBecomeActiveNotification, object: nil ) - + NotificationCenter.default.addObserver( self, selector: #selector(applicationDidEnterBackground(notification:)), name: UIApplication.didEnterBackgroundNotification, object: nil ) - + NotificationCenter.default.addObserver( self, selector: #selector(applicationWillEnterForeground(notification:)), name: UIApplication.willEnterForegroundNotification, object: nil ) - + NotificationCenter.default.addObserver( self, selector: #selector(audioRouteChanged(notification:)), @@ -249,52 +248,52 @@ class RCTVideo: UIView, RCTVideoPlayerViewControllerDelegate, RCTPlayerObserverH object: nil ) _playerObserver._handlers = self -#if USE_VIDEO_CACHING - _videoCache.playerItemPrepareText = playerItemPrepareText -#endif + #if USE_VIDEO_CACHING + _videoCache.playerItemPrepareText = playerItemPrepareText + #endif } - + required init?(coder aDecoder: NSCoder) { super.init(coder: aDecoder) -#if USE_GOOGLE_IMA - _imaAdsManager = RCTIMAAdsManager(video: self, pipEnabled: isPipEnabled) -#endif + #if USE_GOOGLE_IMA + _imaAdsManager = RCTIMAAdsManager(video: self, pipEnabled: isPipEnabled) + #endif } - + deinit { NotificationCenter.default.removeObserver(self) self.removePlayerLayer() _playerObserver.clearPlayer() - + if let player = _player { NowPlayingInfoCenterManager.shared.removePlayer(player: player) } - -#if os(iOS) - _pip = nil -#endif + + #if os(iOS) + _pip = nil + #endif } - + // MARK: - App lifecycle handlers - + @objc func applicationWillResignActive(notification _: NSNotification!) { if _playInBackground || _playWhenInactive || _paused { return } - + _player?.pause() _player?.rate = 0.0 RCTPlayerOperations.removeSpatialAudioRemoteCommandHandler() } - + @objc func applicationDidBecomeActive(notification _: NSNotification!) { if _playInBackground || _playWhenInactive || _paused { return } - + // Resume the player or any other tasks that should continue when the app becomes active. _player?.play() _player?.rate = _rate } - + @objc func applicationDidEnterBackground(notification _: NSNotification!) { if !_playInBackground { @@ -303,7 +302,7 @@ class RCTVideo: UIView, RCTVideoPlayerViewControllerDelegate, RCTPlayerObserverH _playerViewController?.player = nil } } - + @objc func applicationWillEnterForeground(notification _: NSNotification!) { self.applyModifiers() @@ -312,9 +311,9 @@ class RCTVideo: UIView, RCTVideoPlayerViewControllerDelegate, RCTPlayerObserverH _playerViewController?.player = _player } } - + // MARK: - Audio events - + @objc func audioRouteChanged(notification: NSNotification!) { if let userInfo = notification.userInfo { @@ -325,25 +324,25 @@ class RCTVideo: UIView, RCTVideoPlayerViewControllerDelegate, RCTPlayerObserverH } } } - + // MARK: - Progress - + func sendProgressUpdate() { -#if !USE_GOOGLE_IMA - // If we dont use Ads and onVideoProgress is not defined we dont need to run this code - guard onVideoProgress != nil else { return } -#endif - + #if !USE_GOOGLE_IMA + // If we dont use Ads and onVideoProgress is not defined we dont need to run this code + guard onVideoProgress != nil else { return } + #endif + if let video = _player?.currentItem, video == nil || video.status != AVPlayerItem.Status.readyToPlay { return } - + let playerDuration: CMTime = RCTVideoUtils.playerItemDuration(_player) if CMTIME_IS_INVALID(playerDuration) { return } - + var currentTime = _player?.currentTime() if currentTime != nil && _source?.cropStart != nil { currentTime = CMTimeSubtract(currentTime!, CMTimeMake(value: _source?.cropStart ?? 0, timescale: 1000)) @@ -351,18 +350,18 @@ class RCTVideo: UIView, RCTVideoPlayerViewControllerDelegate, RCTPlayerObserverH let currentPlaybackTime = _player?.currentItem?.currentDate() let duration = CMTimeGetSeconds(playerDuration) let currentTimeSecs = CMTimeGetSeconds(currentTime ?? .zero) - + NotificationCenter.default.post(name: NSNotification.Name("RCTVideo_progress"), object: nil, userInfo: [ "progress": NSNumber(value: currentTimeSecs / duration), ]) - + if currentTimeSecs >= 0 { -#if USE_GOOGLE_IMA - if !_didRequestAds && currentTimeSecs >= 0.0001 && _adTagUrl != nil { - _imaAdsManager.requestAds() - _didRequestAds = true - } -#endif + #if USE_GOOGLE_IMA + if !_didRequestAds && currentTimeSecs >= 0.0001 && _adTagUrl != nil { + _imaAdsManager.requestAds() + _didRequestAds = true + } + #endif onVideoProgress?([ "currentTime": NSNumber(value: Float(currentTimeSecs)), "playableDuration": RCTVideoUtils.calculatePlayableDuration(_player, withSource: _source), @@ -373,10 +372,10 @@ class RCTVideo: UIView, RCTVideoPlayerViewControllerDelegate, RCTPlayerObserverH ]) } } - + var isSetSourceOngoing = false var nextSource: NSDictionary? - + func applyNextSource() { if self.nextSource != nil { DebugLog("apply next source") @@ -386,9 +385,9 @@ class RCTVideo: UIView, RCTVideoPlayerViewControllerDelegate, RCTPlayerObserverH self.setSrc(nextSrc) } } - + // MARK: - Player and source - + func preparePlayerItem() async throws -> AVPlayerItem { guard let source = _source else { DebugLog("The source not exist") @@ -396,7 +395,7 @@ class RCTVideo: UIView, RCTVideoPlayerViewControllerDelegate, RCTPlayerObserverH applyNextSource() throw NSError(domain: "", code: 0, userInfo: nil) } - + // Perform on next run loop, otherwise onVideoLoadStart is nil onVideoLoadStart?([ "src": [ @@ -407,12 +406,12 @@ class RCTVideo: UIView, RCTVideoPlayerViewControllerDelegate, RCTPlayerObserverH "drm": _drm?.json ?? NSNull(), "target": reactTag, ]) - + if let uri = source.uri, uri.starts(with: "ph://") { let photoAsset = await RCTVideoUtils.preparePHAsset(uri: uri) return await playerItemPrepareText(asset: photoAsset, assetOptions: nil, uri: source.uri ?? "") } - + guard let assetResult = RCTVideoUtils.prepareAsset(source: source), let asset = assetResult.asset, let assetOptions = assetResult.assetOptions else { @@ -421,7 +420,7 @@ class RCTVideo: UIView, RCTVideoPlayerViewControllerDelegate, RCTPlayerObserverH applyNextSource() throw NSError(domain: "", code: 0, userInfo: nil) } - + guard let assetResult = RCTVideoUtils.prepareAsset(source: source), let asset = assetResult.asset, let assetOptions = assetResult.assetOptions else { @@ -430,17 +429,17 @@ class RCTVideo: UIView, RCTVideoPlayerViewControllerDelegate, RCTPlayerObserverH applyNextSource() throw NSError(domain: "", code: 0, userInfo: nil) } - + if let startPosition = _source?.startPosition { _startPosition = startPosition / 1000 } - -#if USE_VIDEO_CACHING - if _videoCache.shouldCache(source: source, textTracks: _textTracks) { - return try await _videoCache.playerItemForSourceUsingCache(uri: source.uri, assetOptions: assetOptions) - } -#endif - + + #if USE_VIDEO_CACHING + if _videoCache.shouldCache(source: source, textTracks: _textTracks) { + return try await _videoCache.playerItemForSourceUsingCache(uri: source.uri, assetOptions: assetOptions) + } + #endif + if _drm != nil || _localSourceEncryptionKeyScheme != nil { _resouceLoaderDelegate = RCTResourceLoaderDelegate( asset: asset, @@ -451,16 +450,16 @@ class RCTVideo: UIView, RCTVideoPlayerViewControllerDelegate, RCTPlayerObserverH reactTag: reactTag ) } - + return await playerItemPrepareText(asset: asset, assetOptions: assetOptions, uri: source.uri ?? "") } - + func setupPlayer(playerItem: AVPlayerItem) async throws { if !isSetSourceOngoing { DebugLog("setSrc has been canceled last step") return } - + _player?.pause() _playerItem = playerItem _playerObserver.playerItem = _playerItem @@ -470,40 +469,40 @@ class RCTVideo: UIView, RCTVideoPlayerViewControllerDelegate, RCTPlayerObserverH if let maxBitRate = _maxBitRate { _playerItem?.preferredPeakBitRate = Double(maxBitRate) } - + if _player == nil { _player = AVPlayer() _player!.replaceCurrentItem(with: playerItem) - + // We need to register player after we set current item and only for init NowPlayingInfoCenterManager.shared.registerPlayer(player: _player!) } else { _player?.replaceCurrentItem(with: playerItem) - + // later we can just call "updateMetadata: NowPlayingInfoCenterManager.shared.updateMetadata() } - + _playerObserver.player = _player applyModifiers() _player?.actionAtItemEnd = .none - + if #available(iOS 10.0, *) { setAutomaticallyWaitsToMinimizeStalling(_automaticallyWaitsToMinimizeStalling) } - -#if USE_GOOGLE_IMA - if _adTagUrl != nil { - // Set up your content playhead and contentComplete callback. - _contentPlayhead = IMAAVPlayerContentPlayhead(avPlayer: _player!) - - _imaAdsManager.setUpAdsLoader() - } -#endif + + #if USE_GOOGLE_IMA + if _adTagUrl != nil { + // Set up your content playhead and contentComplete callback. + _contentPlayhead = IMAAVPlayerContentPlayhead(avPlayer: _player!) + + _imaAdsManager.setUpAdsLoader() + } + #endif isSetSourceOngoing = false applyNextSource() } - + @objc func setSrc(_ source: NSDictionary!) { if self.isSetSourceOngoing || self.nextSource != nil { @@ -513,18 +512,18 @@ class RCTVideo: UIView, RCTVideoPlayerViewControllerDelegate, RCTPlayerObserverH return } self.isSetSourceOngoing = true - + let initializeSource = { self._source = VideoSource(source) if self._source?.uri == nil || self._source?.uri == "" { self._player?.replaceCurrentItem(with: nil) self.isSetSourceOngoing = false self.applyNextSource() - + if let player = self._player { NowPlayingInfoCenterManager.shared.removePlayer(player: player) } - + DebugLog("setSrc Stopping playback") return } @@ -532,53 +531,53 @@ class RCTVideo: UIView, RCTVideoPlayerViewControllerDelegate, RCTPlayerObserverH self._playerObserver.player = nil self._resouceLoaderDelegate = nil self._playerObserver.playerItem = nil - + // perform on next run loop, otherwise other passed react-props may not be set RCTVideoUtils.delay { [weak self] in do { guard let self else { throw NSError(domain: "", code: 0, userInfo: nil) } - + let playerItem = try await self.preparePlayerItem() try await setupPlayer(playerItem: playerItem) RCTPlayerOperations.addSpatialAudioRemoteCommandHandler() } catch { DebugLog("An error occurred: \(error.localizedDescription)") - + if let self { self.onVideoError?(["error": error.localizedDescription]) self.isSetSourceOngoing = false self.applyNextSource() - + if let player = self._player { NowPlayingInfoCenterManager.shared.removePlayer(player: player) } } } } - + self._videoLoadStarted = true CurrentVideos.shared().add(self, forTag: self.reactTag) self.applyNextSource() } - + DispatchQueue.global(qos: .default).async(execute: initializeSource) } - + @objc func setDrm(_ drm: NSDictionary) { _drm = DRMParams(drm) } - + @objc func setLocalSourceEncryptionKeyScheme(_ keyScheme: String) { _localSourceEncryptionKeyScheme = keyScheme } - + func playerItemPrepareText(asset: AVAsset!, assetOptions: NSDictionary?, uri: String) async -> AVPlayerItem { if (self._textTracks == nil) || self._textTracks?.isEmpty == true || (uri.hasSuffix(".m3u8")) { return await self.playerItemPropegateMetadata(AVPlayerItem(asset: asset)) } - + // AVPlayer can't airplay AVMutableCompositions self._allowsExternalPlayback = false let mixComposition = await RCTVideoUtils.generateMixComposition(asset) @@ -588,137 +587,135 @@ class RCTVideo: UIView, RCTVideoPlayerViewControllerDelegate, RCTPlayerObserverH mixComposition: mixComposition, textTracks: self._textTracks ) - + if validTextTracks.count != self._textTracks?.count { self.setTextTracks(validTextTracks) } - + return await self.playerItemPropegateMetadata(AVPlayerItem(asset: mixComposition)) } - + func playerItemPropegateMetadata(_ playerItem: AVPlayerItem!) async -> AVPlayerItem { var mapping: [AVMetadataIdentifier: Any] = [:] - + if let title = _source?.customMetadata?.title { mapping[.commonIdentifierTitle] = title } - + if let artist = _source?.customMetadata?.artist { mapping[.commonIdentifierArtist] = artist } - + if let subtitle = _source?.customMetadata?.subtitle { mapping[.iTunesMetadataTrackSubTitle] = subtitle } - + if let description = _source?.customMetadata?.description { mapping[.commonIdentifierDescription] = description } - + if let imageUri = _source?.customMetadata?.imageUri, let imageData = await RCTVideoUtils.createImageMetadataItem(imageUri: imageUri) { mapping[.commonIdentifierArtwork] = imageData } - + if #available(iOS 12.2, *), !mapping.isEmpty { playerItem.externalMetadata = RCTVideoUtils.createMetadataItems(for: mapping) } - -#if os(tvOS) - if let chapters = _chapters { - playerItem.navigationMarkerGroups = RCTVideoTVUtils.makeNavigationMarkerGroups(chapters) - } -#endif + + #if os(tvOS) + if let chapters = _chapters { + playerItem.navigationMarkerGroups = RCTVideoTVUtils.makeNavigationMarkerGroups(chapters) + } + #endif return playerItem } - + func getAudioTrackInfo( - model: M3U8PlaylistModel, + model _: M3U8PlaylistModel, masterModel: M3U8PlaylistModel ) -> [String: Any] { - var streamList: NSArray = .init() - - for i in 0..<masterModel.masterPlaylist.xStreamList.count { + + for i in 0 ..< masterModel.masterPlaylist.xStreamList.count { let inf = masterModel.masterPlaylist.xStreamList.xStreamInf(at: i) if let inf { streamList.adding(inf) } } - + if let currentEvent: AVPlayerItemAccessLogEvent = _player?.currentItem?.accessLog()?.events.last { - let predicate: NSPredicate = NSPredicate(format: "%K == %f", "bandwidth", currentEvent.indicatedBitrate) + let predicate = NSPredicate(format: "%K == %f", "bandwidth", currentEvent.indicatedBitrate) let filteredArray = streamList.filtered(using: predicate) let current = filteredArray.last - + if let current = current as? M3U8ExtXStreamInf { let mediaList: NSArray = .init() - for i in 0..<masterModel.masterPlaylist.xMediaList.audio().count { + for i in 0 ..< masterModel.masterPlaylist.xMediaList.audio().count { let inf = masterModel.masterPlaylist.xMediaList.audio().xMedia(at: i) if let inf { mediaList.adding(inf) } } - + let predicate = NSPredicate(format: "SELF.groupId == %@", current.audio) let currentAudio = mediaList.filtered(using: predicate).last - + if let currentAudio = currentAudio as? M3U8ExtXMedia { let url: URL = currentAudio.m3u8URL() let audioModel: M3U8PlaylistModel? = try? .init(url: url) - + if let audioModel { let audioInfo: M3U8SegmentInfo = audioModel.mainMediaPl.segmentList.segmentInfo(at: 0) - + return [ "title": currentAudio.name(), "language": currentAudio.language(), "codecs": currentAudio.groupId(), "file": audioInfo.uri.absoluteString, - "channels": currentAudio.channels + "channels": currentAudio.channels, ] } - } } } - + return .init() } - + func getVideoTrackInfo( model: M3U8PlaylistModel, - masterModel: M3U8PlaylistModel) -> [String: Any] { - - if model.mainMediaPl.segmentList.count > 0 { + masterModel: M3U8PlaylistModel + ) -> [String: Any] { + if !model.mainMediaPl.segmentList.isEmpty { let uri: URL = model.mainMediaPl.segmentList.segmentInfo(at: 0).uri if uri == nil { return .init() } - - var codecs: String = "" - - if masterModel.masterPlaylist.xStreamList.count > 0 { + + var codecs = "" + + if !masterModel.masterPlaylist.xStreamList.isEmpty { if let inf = masterModel.masterPlaylist.xStreamList.xStreamInf(at: 0) { codecs = (inf.codecs as NSArray).componentsJoined(by: ",") } } - + let stringURL = uri.absoluteString as NSString return [ "file": stringURL, - "codecs": codecs + "codecs": codecs, ] } return .init() } - + // MARK: - Prop setters - + @objc func setResizeMode(_ mode: String) { var resizeMode: AVLayerVideoGravity = .resizeAspect - + switch mode { case "contain": resizeMode = .resizeAspect @@ -731,81 +728,81 @@ class RCTVideo: UIView, RCTVideoPlayerViewControllerDelegate, RCTPlayerObserverH default: resizeMode = .resizeAspect } - + if _controls { _playerViewController?.videoGravity = resizeMode } else { _playerLayer?.videoGravity = resizeMode } - + _resizeMode = mode } - + @objc func setPlayInBackground(_ playInBackground: Bool) { _playInBackground = playInBackground } - + @objc func setPreventsDisplaySleepDuringVideoPlayback(_ preventsDisplaySleepDuringVideoPlayback: Bool) { _preventsDisplaySleepDuringVideoPlayback = preventsDisplaySleepDuringVideoPlayback self.applyModifiers() } - + @objc func setAllowsExternalPlayback(_ allowsExternalPlayback: Bool) { _allowsExternalPlayback = allowsExternalPlayback -#if !os(visionOS) - _player?.allowsExternalPlayback = _allowsExternalPlayback -#endif + #if !os(visionOS) + _player?.allowsExternalPlayback = _allowsExternalPlayback + #endif } - + @objc func setPlayWhenInactive(_ playWhenInactive: Bool) { _playWhenInactive = playWhenInactive } - + @objc func setPictureInPicture(_ pictureInPicture: Bool) { -#if os(iOS) - let audioSession = AVAudioSession.sharedInstance() - do { - try audioSession.setCategory(.playback) - try audioSession.setActive(true, options: []) - } catch {} - if pictureInPicture { - _pictureInPictureEnabled = true - } else { - _pictureInPictureEnabled = false - } - _pip?.setPictureInPicture(pictureInPicture) -#endif + #if os(iOS) + let audioSession = AVAudioSession.sharedInstance() + do { + try audioSession.setCategory(.playback) + try audioSession.setActive(true, options: []) + } catch {} + if pictureInPicture { + _pictureInPictureEnabled = true + } else { + _pictureInPictureEnabled = false + } + _pip?.setPictureInPicture(pictureInPicture) + #endif } - + @objc func setRestoreUserInterfaceForPIPStopCompletionHandler(_ restore: Bool) { -#if os(iOS) - if _pip != nil { - _pip?.setRestoreUserInterfaceForPIPStopCompletionHandler(restore) - } else { - _playerObserver.setRestoreUserInterfaceForPIPStopCompletionHandler(restore) - } -#endif + #if os(iOS) + if _pip != nil { + _pip?.setRestoreUserInterfaceForPIPStopCompletionHandler(restore) + } else { + _playerObserver.setRestoreUserInterfaceForPIPStopCompletionHandler(restore) + } + #endif } - + @objc func setIgnoreSilentSwitch(_ ignoreSilentSwitch: String?) { _ignoreSilentSwitch = ignoreSilentSwitch RCTPlayerOperations.configureAudio(ignoreSilentSwitch: _ignoreSilentSwitch, mixWithOthers: _mixWithOthers, audioOutput: _audioOutput) applyModifiers() } - + @objc func setMixWithOthers(_ mixWithOthers: String?) { _mixWithOthers = mixWithOthers applyModifiers() } - + @objc func setPaused(_ paused: Bool) { if self.isManaged() { @@ -814,20 +811,20 @@ class RCTVideo: UIView, RCTVideoPlayerViewControllerDelegate, RCTPlayerObserverH } if paused { if _adPlaying { -#if USE_GOOGLE_IMA - _imaAdsManager.getAdsManager()?.pause() -#endif + #if USE_GOOGLE_IMA + _imaAdsManager.getAdsManager()?.pause() + #endif } else { _player?.pause() _player?.rate = 0.0 } } else { RCTPlayerOperations.configureAudio(ignoreSilentSwitch: _ignoreSilentSwitch, mixWithOthers: _mixWithOthers, audioOutput: _audioOutput) - + if _adPlaying { -#if USE_GOOGLE_IMA - _imaAdsManager.getAdsManager()?.resume() -#endif + #if USE_GOOGLE_IMA + _imaAdsManager.getAdsManager()?.resume() + #endif } else { if #available(iOS 10.0, *), !_automaticallyWaitsToMinimizeStalling { _player?.playImmediately(atRate: _rate) @@ -838,10 +835,10 @@ class RCTVideo: UIView, RCTVideoPlayerViewControllerDelegate, RCTPlayerObserverH _player?.rate = _rate } } - + _paused = paused } - + @objc func setSeek(_ info: NSDictionary!) { if self.isManaged() { @@ -861,7 +858,7 @@ class RCTVideo: UIView, RCTVideoPlayerViewControllerDelegate, RCTPlayerObserverH _pendingSeekTime = seekTime.floatValue return } - + RCTPlayerOperations.seek( player: player, playerItem: item, @@ -870,17 +867,17 @@ class RCTVideo: UIView, RCTVideoPlayerViewControllerDelegate, RCTPlayerObserverH seekTolerance: seekTolerance.floatValue ) { [weak self] (_: Bool) in guard let self else { return } - + self._playerObserver.addTimeObserverIfNotSet() self.setPaused(_paused) self.onVideoSeek?(["currentTime": NSNumber(value: Float(CMTimeGetSeconds(item.currentTime()))), "seekTime": seekTime, "target": self.reactTag]) } - + _pendingSeek = false } - + @objc func setRate(_ rate: Float) { if _rate != 1 { @@ -898,27 +895,27 @@ class RCTVideo: UIView, RCTVideoPlayerViewControllerDelegate, RCTPlayerObserverH self.applyModifiers() } } - + @objc func isMuted() -> Bool { return _muted } - + @objc func setMuted(_ muted: Bool) { _muted = muted applyModifiers() } - + @objc func setAudioOutput(_ audioOutput: String) { _audioOutput = audioOutput RCTPlayerOperations.configureAudio(ignoreSilentSwitch: _ignoreSilentSwitch, mixWithOthers: _mixWithOthers, audioOutput: _audioOutput) do { if audioOutput == "speaker" { -#if os(iOS) || os(visionOS) - try AVAudioSession.sharedInstance().overrideOutputAudioPort(AVAudioSession.PortOverride.speaker) -#endif + #if os(iOS) || os(visionOS) + try AVAudioSession.sharedInstance().overrideOutputAudioPort(AVAudioSession.PortOverride.speaker) + #endif } else if audioOutput == "earpiece" { try AVAudioSession.sharedInstance().overrideOutputAudioPort(AVAudioSession.PortOverride.none) } @@ -926,19 +923,19 @@ class RCTVideo: UIView, RCTVideoPlayerViewControllerDelegate, RCTPlayerObserverH print("Error occurred: \(error.localizedDescription)") } } - + @objc func setVolume(_ volume: Float) { _volume = volume applyModifiers() } - + @objc func setMaxBitRate(_ maxBitRate: Float) { _maxBitRate = maxBitRate _playerItem?.preferredPeakBitRate = Double(maxBitRate) } - + @objc func setPreferredForwardBufferDuration(_ preferredForwardBufferDuration: Float) { _preferredForwardBufferDuration = preferredForwardBufferDuration @@ -948,7 +945,7 @@ class RCTVideo: UIView, RCTVideoPlayerViewControllerDelegate, RCTPlayerObserverH // Fallback on earlier versions } } - + @objc func setAutomaticallyWaitsToMinimizeStalling(_ waits: Bool) { _automaticallyWaitsToMinimizeStalling = waits @@ -958,7 +955,7 @@ class RCTVideo: UIView, RCTVideoPlayerViewControllerDelegate, RCTPlayerObserverH // Fallback on earlier versions } } - + func setPlaybackRange(_ item: AVPlayerItem!, withCropStart cropStart: Int64?, withCropEnd cropEnd: Int64?) { if let cropStart { let start = CMTimeMake(value: cropStart, timescale: 1000) @@ -970,7 +967,7 @@ class RCTVideo: UIView, RCTVideoPlayerViewControllerDelegate, RCTPlayerObserverH item.forwardPlaybackEndTime = CMTimeMake(value: cropEnd, timescale: 1000) } } - + func applyModifiers() { if let video = _player?.currentItem, video == nil || video.status != AVPlayerItem.Status.readyToPlay { @@ -985,19 +982,19 @@ class RCTVideo: UIView, RCTVideoPlayerViewControllerDelegate, RCTPlayerObserverH _player?.volume = _volume _player?.isMuted = false } - + if #available(iOS 12.0, tvOS 12.0, *) { -#if !os(visionOS) - _player?.preventsDisplaySleepDuringVideoPlayback = _preventsDisplaySleepDuringVideoPlayback -#endif + #if !os(visionOS) + _player?.preventsDisplaySleepDuringVideoPlayback = _preventsDisplaySleepDuringVideoPlayback + #endif } else { // Fallback on earlier versions } - + if let _maxBitRate { setMaxBitRate(_maxBitRate) } - + setAudioOutput(_audioOutput) setSelectedAudioTrack(_selectedAudioTrackCriteria) setSelectedTextTrack(_selectedTextTrackCriteria) @@ -1007,17 +1004,17 @@ class RCTVideo: UIView, RCTVideoPlayerViewControllerDelegate, RCTPlayerObserverH setPaused(_paused) setAllowsExternalPlayback(_allowsExternalPlayback) } - + @objc func setRepeat(_ repeat: Bool) { _repeat = `repeat` } - + @objc func setSelectedAudioTrack(_ selectedAudioTrack: NSDictionary?) { setSelectedAudioTrack(SelectedTrackCriteria(selectedAudioTrack)) } - + func setSelectedAudioTrack(_ selectedAudioTrack: SelectedTrackCriteria?) { _selectedAudioTrackCriteria = selectedAudioTrack Task { @@ -1025,12 +1022,12 @@ class RCTVideo: UIView, RCTVideoPlayerViewControllerDelegate, RCTPlayerObserverH criteria: _selectedAudioTrackCriteria) } } - + @objc func setSelectedTextTrack(_ selectedTextTrack: NSDictionary?) { setSelectedTextTrack(SelectedTrackCriteria(selectedTextTrack)) } - + func setSelectedTextTrack(_ selectedTextTrack: SelectedTrackCriteria?) { _selectedTextTrackCriteria = selectedTextTrack if _textTracks != nil { // sideloaded text tracks @@ -1042,28 +1039,28 @@ class RCTVideo: UIView, RCTVideoPlayerViewControllerDelegate, RCTPlayerObserverH } } } - + @objc func setTextTracks(_ textTracks: [NSDictionary]?) { setTextTracks(textTracks?.map { TextTrack($0) }) } - + func setTextTracks(_ textTracks: [TextTrack]?) { _textTracks = textTracks - + // in case textTracks was set after selectedTextTrack if _selectedTextTrackCriteria != nil { setSelectedTextTrack(_selectedTextTrackCriteria) } } - + @objc func setChapters(_ chapters: [NSDictionary]?) { setChapters(chapters?.map { Chapter($0) }) } - + func setChapters(_ chapters: [Chapter]?) { _chapters = chapters } - + @objc func setFullscreen(_ fullscreen: Bool) { if fullscreen && !_fullscreenPlayerPresented && _player != nil { @@ -1072,15 +1069,15 @@ class RCTVideo: UIView, RCTVideoPlayerViewControllerDelegate, RCTPlayerObserverH if _playerViewController == nil { self.usePlayerViewController() } - + // Set presentation style to fullscreen _playerViewController?.modalPresentationStyle = .fullScreen - + // Find the nearest view controller var viewController: UIViewController! = self.firstAvailableUIViewController() if viewController == nil { guard let keyWindow = RCTVideoUtils.getCurrentWindow() else { return } - + viewController = keyWindow.rootViewController if !viewController.children.isEmpty { viewController = viewController.children.last @@ -1088,22 +1085,22 @@ class RCTVideo: UIView, RCTVideoPlayerViewControllerDelegate, RCTPlayerObserverH } if viewController != nil { _presentingViewController = viewController - + self.onVideoFullscreenPlayerWillPresent?(["target": reactTag as Any]) - + if let playerViewController = _playerViewController { if _controls { // prevents crash https://github.com/TheWidlarzGroup/react-native-video/issues/3040 self._playerViewController?.removeFromParent() } - + viewController.present(playerViewController, animated: true, completion: { [weak self] in guard let self else { return } // In fullscreen we must display controls self._playerViewController?.showsPlaybackControls = true self._fullscreenPlayerPresented = fullscreen self._playerViewController?.autorotate = self._fullscreenAutorotate - + self.onVideoFullscreenPlayerDidPresent?(["target": self.reactTag]) }) } @@ -1115,7 +1112,7 @@ class RCTVideo: UIView, RCTVideoPlayerViewControllerDelegate, RCTPlayerObserverH }) } } - + @objc func setFullscreenAutorotate(_ autorotate: Bool) { _fullscreenAutorotate = autorotate @@ -1123,7 +1120,7 @@ class RCTVideo: UIView, RCTVideoPlayerViewControllerDelegate, RCTPlayerObserverH _playerViewController?.autorotate = autorotate } } - + @objc func setFullscreenOrientation(_ orientation: String?) { _fullscreenOrientation = orientation @@ -1131,37 +1128,37 @@ class RCTVideo: UIView, RCTVideoPlayerViewControllerDelegate, RCTPlayerObserverH _playerViewController?.preferredOrientation = orientation } } - + func usePlayerViewController() { guard let _player, let _playerItem else { return } - + if _playerViewController == nil { _playerViewController = createPlayerViewController(player: _player, withPlayerItem: _playerItem) } // to prevent video from being animated when resizeMode is 'cover' // resize mode must be set before subview is added setResizeMode(_resizeMode) - + guard let _playerViewController else { return } - + if _controls { let viewController: UIViewController! = self.reactViewController() viewController?.addChild(_playerViewController) self.addSubview(_playerViewController.view) } - + _playerObserver.playerViewController = _playerViewController } - + func createPlayerViewController(player: AVPlayer, withPlayerItem _: AVPlayerItem) -> RCTVideoPlayerViewController { let viewController = RCTVideoPlayerViewController() viewController.showsPlaybackControls = self._controls -#if !os(tvOS) - viewController.updatesNowPlayingInfoCenter = false -#endif + #if !os(tvOS) + viewController.updatesNowPlayingInfoCenter = false + #endif viewController.rctDelegate = self viewController.preferredOrientation = _fullscreenOrientation - + viewController.view.frame = self.bounds viewController.player = player if #available(tvOS 14.0, *) { @@ -1169,30 +1166,30 @@ class RCTVideo: UIView, RCTVideoPlayerViewControllerDelegate, RCTPlayerObserverH } return viewController } - + func usePlayerLayer() { if let _player { _playerLayer = AVPlayerLayer(player: _player) _playerLayer?.frame = self.bounds _playerLayer?.needsDisplayOnBoundsChange = true - + // to prevent video from being animated when resizeMode is 'cover' // resize mode must be set before layer is added setResizeMode(_resizeMode) _playerObserver.playerLayer = _playerLayer - + if let _playerLayer { self.layer.addSublayer(_playerLayer) } self.layer.needsDisplayOnBoundsChange = true -#if os(iOS) - if _pictureInPictureEnabled { - _pip?.setupPipController(_playerLayer) - } -#endif + #if os(iOS) + if _pictureInPictureEnabled { + _pip?.setupPipController(_playerLayer) + } + #endif } } - + @objc func setControls(_ controls: Bool) { if _controls != controls || ((_playerLayer == nil) && (_playerViewController == nil)) { @@ -1209,51 +1206,51 @@ class RCTVideo: UIView, RCTVideoPlayerViewControllerDelegate, RCTPlayerObserverH } } } - + @objc func setShowNotificationControls(_ showNotificationControls: Bool) { guard let player = _player else { return } - + if showNotificationControls { NowPlayingInfoCenterManager.shared.registerPlayer(player: player) } else { NowPlayingInfoCenterManager.shared.removePlayer(player: player) } - + _showNotificationControls = showNotificationControls } - + @objc func setProgressUpdateInterval(_ progressUpdateInterval: Float) { _playerObserver.replaceTimeObserverIfSet(Float64(progressUpdateInterval)) } - + func removePlayerLayer() { _playerLayer?.removeFromSuperlayer() _playerLayer = nil _playerObserver.playerLayer = nil RCTPlayerOperations.removeSpatialAudioRemoteCommandHandler() } - + @objc func setSubtitleStyle(_ style: [String: Any]) { let subtitleStyle = SubtitleStyle.parse(from: style) _playerObserver.subtitleStyle = subtitleStyle } - + // MARK: - RCTVideoPlayerViewControllerDelegate - + func videoPlayerViewControllerWillDismiss(playerViewController: AVPlayerViewController) { if _playerViewController == playerViewController && _fullscreenPlayerPresented, - let onVideoFullscreenPlayerWillDismiss { + let onVideoFullscreenPlayerWillDismiss { _playerObserver.removePlayerViewControllerObservers() onVideoFullscreenPlayerWillDismiss(["target": reactTag as Any]) } } - + func videoPlayerViewControllerDidDismiss(playerViewController: AVPlayerViewController) { if _playerViewController == playerViewController && _fullscreenPlayerPresented { _fullscreenPlayerPresented = false @@ -1261,15 +1258,15 @@ class RCTVideo: UIView, RCTVideoPlayerViewControllerDelegate, RCTPlayerObserverH _playerViewController = nil _playerObserver.playerViewController = nil self.applyModifiers() - + onVideoFullscreenPlayerDidDismiss?(["target": reactTag as Any]) } } - + @objc func setFilter(_ filterName: String!) { _filterName = filterName - + if !_filterEnabled { return } else if let uri = _source?.uri, uri.contains("m3u8") { @@ -1277,41 +1274,41 @@ class RCTVideo: UIView, RCTVideoPlayerViewControllerDelegate, RCTPlayerObserverH } else if _playerItem?.asset == nil { return } - + let filter: CIFilter! = CIFilter(name: filterName) Task { let composition = await RCTVideoUtils.generateVideoComposition(asset: _playerItem!.asset, filter: filter) self._playerItem?.videoComposition = composition } } - + @objc func setFilterEnabled(_ filterEnabled: Bool) { _filterEnabled = filterEnabled } - + // MARK: - RCTIMAAdsManager - + func getAdTagUrl() -> String? { return _adTagUrl } - + @objc func setAdTagUrl(_ adTagUrl: String!) { _adTagUrl = adTagUrl } - -#if USE_GOOGLE_IMA - func getContentPlayhead() -> IMAAVPlayerContentPlayhead? { - return _contentPlayhead - } -#endif + + #if USE_GOOGLE_IMA + func getContentPlayhead() -> IMAAVPlayerContentPlayhead? { + return _contentPlayhead + } + #endif func setAdPlaying(_ adPlaying: Bool) { _adPlaying = adPlaying } - + // MARK: - React View Management - + func insertReactSubview(view: UIView!, atIndex: Int) { if _controls { view.frame = self.bounds @@ -1321,7 +1318,7 @@ class RCTVideo: UIView, RCTVideoPlayerViewControllerDelegate, RCTPlayerObserverH } return } - + func removeReactSubview(subview: UIView!) { if _controls { subview.removeFromSuperview() @@ -1330,12 +1327,12 @@ class RCTVideo: UIView, RCTVideoPlayerViewControllerDelegate, RCTPlayerObserverH } return } - + override func layoutSubviews() { super.layoutSubviews() if _controls, let _playerViewController { _playerViewController.view.frame = bounds - + // also adjust all subviews of contentOverlayView for subview in _playerViewController.contentOverlayView?.subviews ?? [] { subview.frame = bounds @@ -1347,25 +1344,25 @@ class RCTVideo: UIView, RCTVideoPlayerViewControllerDelegate, RCTPlayerObserverH CATransaction.commit() } } - + // MARK: - Lifecycle - + override func removeFromSuperview() { if let player = _player { player.pause() NowPlayingInfoCenterManager.shared.removePlayer(player: player) } - + _player = nil _resouceLoaderDelegate = nil _playerObserver.clearPlayer() - -#if USE_GOOGLE_IMA - _imaAdsManager.releaseAds() -#endif - + + #if USE_GOOGLE_IMA + _imaAdsManager.releaseAds() + #endif + self.removePlayerLayer() - + if let _playerViewController { _playerViewController.view.removeFromSuperview() _playerViewController.removeFromParent() @@ -1374,16 +1371,16 @@ class RCTVideo: UIView, RCTVideoPlayerViewControllerDelegate, RCTPlayerObserverH self._playerViewController = nil _playerObserver.playerViewController = nil } - + _eventDispatcher = nil // swiftlint:disable:next notification_center_detachment NotificationCenter.default.removeObserver(self) - + super.removeFromSuperview() } - + // MARK: - Export - + @objc func save(options: NSDictionary!, resolve: @escaping RCTPromiseResolveBlock, reject: @escaping RCTPromiseRejectBlock) { RCTVideoSave.save( @@ -1393,46 +1390,46 @@ class RCTVideo: UIView, RCTVideoPlayerViewControllerDelegate, RCTPlayerObserverH playerItem: _playerItem ) } - + func setLicenseResult(_ license: String!, _ licenseUrl: String!) { _resouceLoaderDelegate?.setLicenseResult(license, licenseUrl) } - + func setLicenseResultError(_ error: String!, _ licenseUrl: String!) { _resouceLoaderDelegate?.setLicenseResultError(error, licenseUrl) } - + func requestedCurrentTime(_ requestId: NSNumber!) { if let onCommandResult { return } - + let result: [NSString: NSNumber] = [ "requestId": requestId, - "result": getCurrentTime() + "result": getCurrentTime(), ] onCommandResult?(result) } - + private func getCurrentTime() -> NSNumber { let time = _playerItem != nil ? CMTimeGetSeconds(_playerItem?.currentTime() ?? .zero) : 0 return NSNumber(value: time) } - + func dismissFullscreenPlayer() { setFullscreen(false) } - + func presentFullscreenPlayer() { setFullscreen(true) } - + // MARK: - RCTPlayerObserverHandler - + func handleTimeUpdate(time _: CMTime) { sendProgressUpdate() } - + func handleReadyForDisplay(changeObject _: Any, change _: NSKeyValueObservedChange<Bool>) { if _isBuffering { _isBuffering = false @@ -1442,43 +1439,43 @@ class RCTVideo: UIView, RCTVideoPlayerViewControllerDelegate, RCTPlayerObserverH "target": reactTag, ]) } - + // When timeMetadata is read the event onTimedMetadata is triggered func handleTimeMetadataChange(timedMetadata: [AVMetadataItem]) { guard onTimedMetadata != nil else { return } - + var metadata: [[String: String?]?] = [] for item in timedMetadata { let value = item.value as? String let identifier = item.identifier?.rawValue - + if let value { metadata.append(["value": value, "identifier": identifier]) } } - + onTimedMetadata?([ "target": reactTag, "metadata": metadata, ]) } - + // Handle player item status change. func handlePlayerItemStatusChange(playerItem _: AVPlayerItem, change _: NSKeyValueObservedChange<AVPlayerItem.Status>) { guard let _playerItem else { return } - + if _playerItem.status == .readyToPlay { handleReadyToPlay() } else if _playerItem.status == .failed { handlePlaybackFailed() } } - + func handleReadyToPlay() { guard let _playerItem else { return } - + Task { if self._pendingSeek { self.setSeek([ @@ -1487,7 +1484,7 @@ class RCTVideo: UIView, RCTVideoPlayerViewControllerDelegate, RCTPlayerObserverH ]) self._pendingSeek = false } - + if self._startPosition >= 0 { self.setSeek([ "time": NSNumber(value: self._startPosition), @@ -1495,11 +1492,11 @@ class RCTVideo: UIView, RCTVideoPlayerViewControllerDelegate, RCTPlayerObserverH ]) self._startPosition = -1 } - + if onVideoLoad != nil, self._videoLoadStarted { self.update(state: .loaded) var duration = Float(CMTimeGetSeconds(_playerItem.asset.duration)) - + if duration.isNaN || duration == 0 { // This is a safety check for live video. // AVPlayer report a 0 duration @@ -1508,11 +1505,11 @@ class RCTVideo: UIView, RCTVideoPlayerViewControllerDelegate, RCTPlayerObserverH duration = 0 } } - + var width: Float = 0 var height: Float = 0 var orientation = "undefined" - + let tracks = await RCTVideoAssetsUtils.getTracks(asset: _playerItem.asset, withMediaType: .video) var presentationSize = _playerItem.presentationSize if presentationSize.height != 0.0 { @@ -1524,7 +1521,7 @@ class RCTVideo: UIView, RCTVideoPlayerViewControllerDelegate, RCTPlayerObserverH height = Float(naturalSize.height) } orientation = width > height ? "landscape" : width == height ? "square" : "portrait" - + let audioTracks = await RCTVideoUtils.getAudioTrackInfo(self._player) let textTracks = await RCTVideoUtils.getTextTrackInfo(self._player) self.onVideoLoad?(["duration": NSNumber(value: duration), @@ -1536,30 +1533,30 @@ class RCTVideo: UIView, RCTVideoPlayerViewControllerDelegate, RCTPlayerObserverH "canStepBackward": NSNumber(value: _playerItem.canStepBackward), "canStepForward": NSNumber(value: _playerItem.canStepForward), "naturalSize": [ - "width": width, - "height": height, - "orientation": orientation, + "width": width, + "height": height, + "orientation": orientation, ], "audioTracks": audioTracks, "textTracks": self._textTracks?.compactMap { $0.json } ?? textTracks.map(\.json), "target": self.reactTag as Any]) } - + self._videoLoadStarted = false self.handleMetadataUpdateForTrackChange() self._playerObserver.attachPlayerEventListeners() self.applyModifiers() } } - + func handleMetadataUpdateForTrackChange() { if onPlayedTracksChange != nil { let urlString: String = _player?.currentItem?.accessLog()?.events.last?.uri ?? "" let url = NSURL(string: urlString) let asset: AVAsset? = _player?.currentItem?.asset - + let masterURL: NSURL? = (_player?.currentItem?.asset as? AVURLAsset)?.url as? NSURL - + masterURL?.m3u_loadAsyncCompletion { masterModel, _ in if let url { url.m3u_loadAsyncCompletion { model, _ in @@ -1568,7 +1565,7 @@ class RCTVideo: UIView, RCTVideoPlayerViewControllerDelegate, RCTPlayerObserverH [ "audioTrack": self.getAudioTrackInfo(model: model, masterModel: masterModel), "textTrack": self._textTracks, - "videoTrack": self.getVideoTrackInfo(model: model, masterModel: masterModel) + "videoTrack": self.getVideoTrackInfo(model: model, masterModel: masterModel), ] ) } @@ -1577,12 +1574,12 @@ class RCTVideo: UIView, RCTVideoPlayerViewControllerDelegate, RCTPlayerObserverH } } } - + func handlePlaybackFailed() { if let player = _player { NowPlayingInfoCenterManager.shared.removePlayer(player: player) } - + guard let _playerItem else { return } onVideoError?( [ @@ -1590,22 +1587,22 @@ class RCTVideo: UIView, RCTVideoPlayerViewControllerDelegate, RCTPlayerObserverH "code": NSNumber(value: (_playerItem.error! as NSError).code), "localizedDescription": _playerItem.error?.localizedDescription == nil ? "" : _playerItem.error?.localizedDescription, "localizedFailureReason": ((_playerItem.error! as NSError).localizedFailureReason == nil ? - "" : (_playerItem.error! as NSError).localizedFailureReason) ?? "", + "" : (_playerItem.error! as NSError).localizedFailureReason) ?? "", "localizedRecoverySuggestion": ((_playerItem.error! as NSError).localizedRecoverySuggestion == nil ? - "" : (_playerItem.error! as NSError).localizedRecoverySuggestion) ?? "", + "" : (_playerItem.error! as NSError).localizedRecoverySuggestion) ?? "", "domain": (_playerItem.error as! NSError).domain, ], "target": reactTag, ] ) } - + func handlePlaybackBufferKeyEmpty(playerItem _: AVPlayerItem, change _: NSKeyValueObservedChange<Bool>) { if !_isBuffering { _isBuffering = true } } - + // Continue playing (or not if paused) after being paused due to hitting an unbuffered zone. func handlePlaybackLikelyToKeepUp(playerItem _: AVPlayerItem, change _: NSKeyValueObservedChange<Bool>) { if self.isManaged() { @@ -1621,12 +1618,12 @@ class RCTVideo: UIView, RCTVideoPlayerViewControllerDelegate, RCTPlayerObserverH _isBuffering = false self.onVideoBuffer?(["isBuffering": _isBuffering, "target": self.reactTag as Any]) } - + if _isBuffering { _isBuffering = false } } - + func handleTimeControlStatusChange(player: AVPlayer, change: NSKeyValueObservedChange<AVPlayer.TimeControlStatus>) { if player.timeControlStatus == change.oldValue && change.oldValue != nil { return @@ -1635,61 +1632,61 @@ class RCTVideo: UIView, RCTVideoPlayerViewControllerDelegate, RCTPlayerObserverH return } let isPlaying = player.timeControlStatus == .playing - + guard _isPlaying == nil || _isPlaying! != isPlaying else { return } _isPlaying = isPlaying onVideoPlaybackStateChanged?(["isPlaying": isPlaying, "target": reactTag as Any]) } - + func handlePlaybackRateChange(player: AVPlayer, change: NSKeyValueObservedChange<Float>) { guard let _player else { return } - + if player.rate == change.oldValue && change.oldValue != nil { return } - + onPlaybackRateChange?(["playbackRate": NSNumber(value: _player.rate), "target": reactTag as Any]) - + if _playbackStalled && _player.rate > 0 { onPlaybackResume?(["playbackRate": NSNumber(value: _player.rate), "target": reactTag as Any]) _playbackStalled = false } } - + func handleVolumeChange(player: AVPlayer, change: NSKeyValueObservedChange<Float>) { guard let _player, onVolumeChange != nil else { return } - + if player.rate == change.oldValue && change.oldValue != nil { return } - + onVolumeChange?(["volume": NSNumber(value: _player.volume), "target": reactTag as Any]) } - + func handleExternalPlaybackActiveChange(player _: AVPlayer, change _: NSKeyValueObservedChange<Bool>) { -#if !os(visionOS) - guard let _player, onVideoExternalPlaybackChange != nil else { return } - onVideoExternalPlaybackChange?(["isExternalPlaybackActive": NSNumber(value: _player.isExternalPlaybackActive), - "target": reactTag as Any]) -#endif + #if !os(visionOS) + guard let _player, onVideoExternalPlaybackChange != nil else { return } + onVideoExternalPlaybackChange?(["isExternalPlaybackActive": NSNumber(value: _player.isExternalPlaybackActive), + "target": reactTag as Any]) + #endif } - + func handleViewControllerOverlayViewFrameChange(overlayView _: UIView, change: NSKeyValueObservedChange<CGRect>) { let oldRect = change.oldValue let newRect = change.newValue - + guard let bounds = RCTVideoUtils.getCurrentWindow()?.bounds else { return } - + if !oldRect!.equalTo(newRect!) { // https://github.com/TheWidlarzGroup/react-native-video/issues/3085#issuecomment-1557293391 if newRect!.equalTo(bounds) { RCTLog("in fullscreen") if !_fullscreenUncontrolPlayerPresented { _fullscreenUncontrolPlayerPresented = true - + self.onVideoFullscreenPlayerWillPresent?(["target": self.reactTag as Any]) self.onVideoFullscreenPlayerDidPresent?(["target": self.reactTag as Any]) } @@ -1697,21 +1694,21 @@ class RCTVideo: UIView, RCTVideoPlayerViewControllerDelegate, RCTPlayerObserverH NSLog("not fullscreen") if _fullscreenUncontrolPlayerPresented { _fullscreenUncontrolPlayerPresented = false - + self.onVideoFullscreenPlayerWillDismiss?(["target": self.reactTag as Any]) self.onVideoFullscreenPlayerDidDismiss?(["target": self.reactTag as Any]) } } - + self.reactViewController().view.frame = bounds self.reactViewController().view.setNeedsLayout() } } - + @objc func handleDidFailToFinishPlaying(notification: NSNotification!) { guard onVideoError != nil else { return } - + let error: NSError! = notification.userInfo?[AVPlayerItemFailedToPlayToEndTimeErrorKey] as? NSError onVideoError?( [ @@ -1726,24 +1723,24 @@ class RCTVideo: UIView, RCTVideoPlayerViewControllerDelegate, RCTPlayerObserverH ] ) } - + @objc func handlePlaybackStalled(notification _: NSNotification!) { onPlaybackStalled?(["target": reactTag as Any]) _playbackStalled = true } - + @objc func handlePlayerItemDidReachEnd(notification: NSNotification!) { onVideoEnd?(["target": reactTag as Any]) -#if USE_GOOGLE_IMA - if notification.object as? AVPlayerItem == _player?.currentItem { - _imaAdsManager.getAdsLoader()?.contentComplete() - } -#endif + #if USE_GOOGLE_IMA + if notification.object as? AVPlayerItem == _player?.currentItem { + _imaAdsManager.getAdsLoader()?.contentComplete() + } + #endif if _repeat { let item: AVPlayerItem! = notification.object as? AVPlayerItem - + item.seek( to: _source?.cropStart != nil ? CMTime(value: _source!.cropStart!, timescale: 1000) : CMTime.zero, toleranceBefore: CMTime.zero, @@ -1757,19 +1754,19 @@ class RCTVideo: UIView, RCTVideoPlayerViewControllerDelegate, RCTPlayerObserverH _playerObserver.removePlayerTimeObserver() } } - + @objc func handleAVPlayerAccess(notification: NSNotification!) { guard onVideoBandwidthUpdate != nil else { return } - + guard let accessLog = (notification.object as? AVPlayerItem)?.accessLog() else { return } - + guard let lastEvent = accessLog.events.last else { return } onVideoBandwidthUpdate?(["bitrate": lastEvent.observedBitrate, "target": reactTag]) } - + func handleTracksChange(playerItem _: AVPlayerItem, change _: NSKeyValueObservedChange<[AVPlayerItemTrack]>) { if onTextTracks != nil { Task { @@ -1777,7 +1774,7 @@ class RCTVideo: UIView, RCTVideoPlayerViewControllerDelegate, RCTPlayerObserverH self.onTextTracks?(["textTracks": self._textTracks?.compactMap { $0.json } ?? textTracks.compactMap(\.json)]) } } - + if onAudioTracks != nil { Task { let audioTracks = await RCTVideoUtils.getAudioTrackInfo(self._player) @@ -1785,53 +1782,53 @@ class RCTVideo: UIView, RCTVideoPlayerViewControllerDelegate, RCTPlayerObserverH } } } - + func handleLegibleOutput(strings: [NSAttributedString]) { guard onTextTrackDataChanged != nil else { return } - + if let subtitles = strings.first { self.onTextTrackDataChanged?(["subtitleTracks": subtitles.string]) } } - + // Workaround for #3418 - https://github.com/TheWidlarzGroup/react-native-video/issues/3418#issuecomment-2043508862 @objc func setOnClick(_: Any) {} - + func setMasterVideo(tag: NSNumber) { _masterVideo = tag } - + func setSlaveVideo(tag: NSNumber) { _slaveVideo = tag } - + func isMaster() -> Bool { _slaveVideo != nil } - + func isSlave() -> Bool { _masterVideo != nil } - + func slave() -> RCTVideo? { guard let video = _slaveVideo else { return nil } return CurrentVideos.shared().video(forTag: video) } - + func master() -> RCTVideo? { guard let video = _masterVideo else { return nil } return CurrentVideos.shared().video(forTag: video) } - + func isManaged() -> Bool { isSlave() || isMaster() } - + func setManagedPaused(paused: Bool) { if isSlave() { return @@ -1851,7 +1848,7 @@ class RCTVideo: UIView, RCTVideoPlayerViewControllerDelegate, RCTPlayerObserverH return } RCTPlayerOperations.configureAudio(ignoreSilentSwitch: _ignoreSilentSwitch, mixWithOthers: _mixWithOthers, audioOutput: _audioOutput) - + if #available(iOS 10.0, *), !_automaticallyWaitsToMinimizeStalling { _player?.playImmediately(atRate: _rate) slavePlayer?.playImmediately(atRate: _rate) @@ -1865,19 +1862,19 @@ class RCTVideo: UIView, RCTVideoPlayerViewControllerDelegate, RCTPlayerObserverH _paused = paused slave.setRaw(paused: paused) } - + func isVideoReady() -> Bool { ((_videoState.rawValue & VideoState.loaded.rawValue) != 0) && ((_videoState.rawValue & VideoState.ready.rawValue) != 0) } - + func setRaw(paused: Bool) { _paused = paused } - + func player() -> AVPlayer? { _player } - + func setSeekManaged(info: NSDictionary) { if !isMaster() { return @@ -1887,34 +1884,34 @@ class RCTVideo: UIView, RCTVideoPlayerViewControllerDelegate, RCTPlayerObserverH } let seekTime: NSNumber? = info["time"] as? NSNumber let seekTolerance: NSNumber? = info["tolerance"] as? NSNumber - + let timeScale: Int32 = 1000 - + let item: AVPlayerItem? = _player?.currentItem let slavePlayer: AVPlayer? = slave.player() let slaveItem: AVPlayerItem? = slavePlayer?.currentItem if let item, item.status == .readyToPlay, let slaveItem, slaveItem.status == .readyToPlay { // TODO: check loadedTimeRanges - + let cmSeekTime: CMTime = CMTimeMakeWithSeconds(Float64(seekTime?.floatValue ?? .zero), preferredTimescale: timeScale) let current: CMTime = item.currentTime() let tolerance: CMTime = CMTimeMake(value: Int64(seekTolerance?.floatValue ?? .zero), timescale: timeScale) - + if CMTimeCompare(current, cmSeekTime) != 0 { let wasPaused = _paused _player?.pause() slavePlayer?.pause() - + let seekGroup: DispatchGroup = .init() seekGroup.enter() - _player?.seek(to: cmSeekTime, toleranceBefore: tolerance, toleranceAfter: tolerance, completionHandler: { finished in + _player?.seek(to: cmSeekTime, toleranceBefore: tolerance, toleranceAfter: tolerance, completionHandler: { _ in seekGroup.leave() }) seekGroup.enter() - slavePlayer?.seek(to: cmSeekTime, toleranceBefore: tolerance, toleranceAfter: tolerance, completionHandler: { finished in + slavePlayer?.seek(to: cmSeekTime, toleranceBefore: tolerance, toleranceAfter: tolerance, completionHandler: { _ in seekGroup.leave() }) - + seekGroup.notify(queue: .main) { self.seekCompletedFor(seekTime: seekTime ?? 0) slave.seekCompletedFor(seekTime: seekTime ?? 0) @@ -1929,7 +1926,7 @@ class RCTVideo: UIView, RCTVideoPlayerViewControllerDelegate, RCTPlayerObserverH } } } - + func seekCompletedFor(seekTime: NSNumber) { let item: AVPlayerItem? = _player?.currentItem _playerObserver.addTimeObserverIfNotSet() @@ -1938,12 +1935,12 @@ class RCTVideo: UIView, RCTVideoPlayerViewControllerDelegate, RCTPlayerObserverH [ "currentTime": NSNumber(value: CMTimeGetSeconds(item?.currentTime() ?? .zero)), "seekTime": seekTime, - "target": self.reactTag + "target": self.reactTag, ] ) } } - + func update(state: VideoState) { if !self.isManaged() { return @@ -1954,7 +1951,7 @@ class RCTVideo: UIView, RCTVideoPlayerViewControllerDelegate, RCTPlayerObserverH master?.onSlaveVideoStatusChange() } } - + func onSlaveVideoStatusChange() { let slave = self.slave() if slave != nil { @@ -1969,4 +1966,3 @@ class RCTVideo: UIView, RCTVideoPlayerViewControllerDelegate, RCTPlayerObserverH } } } - From b94214a0a3025d65ba983be343eaa32b2d0f743e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pawel=20Lozi=C5=84ski?= <pawel.lozinski@miquido.com> Date: Thu, 13 Jun 2024 22:50:14 +0200 Subject: [PATCH 17/37] make lint happy --- ios/Video/Features/RCTPlayerOperations.swift | 34 +++-- ios/Video/RCTVideo.swift | 132 +++++++++---------- ios/Video/RCTVideoManager.swift | 2 +- 3 files changed, 81 insertions(+), 87 deletions(-) diff --git a/ios/Video/Features/RCTPlayerOperations.swift b/ios/Video/Features/RCTPlayerOperations.swift index 445dce9bec..c3a0bf43d8 100644 --- a/ios/Video/Features/RCTPlayerOperations.swift +++ b/ios/Video/Features/RCTPlayerOperations.swift @@ -10,9 +10,8 @@ let RCTVideoUnset = -1 * Collection of mutating functions */ enum RCTPlayerOperations { - static var remoteCommandHandlerForSpatialAudio: Any? - + static func setSideloadedText(player: AVPlayer?, textTracks: [TextTrack], criteria: SelectedTrackCriteria?) { let type = criteria?.type @@ -120,20 +119,20 @@ enum RCTPlayerOperations { } } } else { // default. invalid type or "system" - #if os(tvOS) + #if os(tvOS) // Do noting. Fix for tvOS native audio menu language selector - #else - await player?.currentItem?.selectMediaOptionAutomatically(in: group) - #endif + #else + await player?.currentItem?.selectMediaOptionAutomatically(in: group) + #endif return } // If a match isn't found, option will be nil and text tracks will be disabled - #if os(tvOS) + #if os(tvOS) // Do noting. Fix for tvOS native audio menu language selector - #else - // If a match isn't found, option will be nil and text tracks will be disabled - await player?.currentItem?.select(mediaOption, in: group) - #endif + #else + // If a match isn't found, option will be nil and text tracks will be disabled + await player?.currentItem?.select(mediaOption, in: group) + #endif } static func seek(player: AVPlayer, playerItem: AVPlayerItem, paused: Bool, seekTime: Float, seekTolerance: Float, completion: @escaping (Bool) -> Void) { @@ -208,25 +207,24 @@ enum RCTPlayerOperations { } } } - + // MARK: - Spatial Audio / Dolby Atmos Workaround /* These functions are a temporarily workaround to enable the rendering of Dolby Atmos on * iOS 15 and above. */ - + static func addSpatialAudioRemoteCommandHandler() { let command = MPRemoteCommandCenter.shared().playCommand - - remoteCommandHandlerForSpatialAudio = command.addTarget(handler: { event in + + remoteCommandHandlerForSpatialAudio = command.addTarget(handler: { _ in MPRemoteCommandHandlerStatus.success }) } - + static func removeSpatialAudioRemoteCommandHandler() { - let command = MPRemoteCommandCenter.shared().playCommand - + if let remoteCommand = RCTPlayerOperations.remoteCommandHandlerForSpatialAudio { command.removeTarget(remoteCommand) RCTPlayerOperations.remoteCommandHandlerForSpatialAudio = nil diff --git a/ios/Video/RCTVideo.swift b/ios/Video/RCTVideo.swift index 15b63b8835..bd9e5b80d4 100644 --- a/ios/Video/RCTVideo.swift +++ b/ios/Video/RCTVideo.swift @@ -75,10 +75,10 @@ class RCTVideo: UIView, RCTVideoPlayerViewControllerDelegate, RCTPlayerObserverH private var _presentingViewController: UIViewController? private var _startPosition: Float64 = -1 private var _showNotificationControls = false - private var _masterVideo: NSNumber? - private var _slaveVideo: NSNumber? + private var _principalVideo: NSNumber? + private var _peripheralVideo: NSNumber? private var _videoState: VideoState = .unknown - private var _masterPendingPlayRequest = false + private var _principalPendingPlayRequest = false private var _pictureInPictureEnabled = false { didSet { #if os(iOS) @@ -112,10 +112,6 @@ class RCTVideo: UIView, RCTVideoPlayerViewControllerDelegate, RCTPlayerObserverH private var _resouceLoaderDelegate: RCTResourceLoaderDelegate? private var _playerObserver: RCTPlayerObserver = .init() - #if USE_VIDEO_CACHING - private let _videoCache: RCTVideoCachingHandler = .init() - #endif - #if os(iOS) private var _pip: RCTPictureInPicture? #endif @@ -633,12 +629,12 @@ class RCTVideo: UIView, RCTVideoPlayerViewControllerDelegate, RCTPlayerObserverH func getAudioTrackInfo( model _: M3U8PlaylistModel, - masterModel: M3U8PlaylistModel + principalModel: M3U8PlaylistModel ) -> [String: Any] { var streamList: NSArray = .init() - for i in 0 ..< masterModel.masterPlaylist.xStreamList.count { - let inf = masterModel.masterPlaylist.xStreamList.xStreamInf(at: i) + for i in 0 ..< principalModel.masterPlaylist.xStreamList.count { + let inf = principalModel.masterPlaylist.xStreamList.xStreamInf(at: i) if let inf { streamList.adding(inf) } @@ -651,8 +647,8 @@ class RCTVideo: UIView, RCTVideoPlayerViewControllerDelegate, RCTPlayerObserverH if let current = current as? M3U8ExtXStreamInf { let mediaList: NSArray = .init() - for i in 0 ..< masterModel.masterPlaylist.xMediaList.audio().count { - let inf = masterModel.masterPlaylist.xMediaList.audio().xMedia(at: i) + for i in 0 ..< principalModel.masterPlaylist.xMediaList.audio().count { + let inf = principalModel.masterPlaylist.xMediaList.audio().xMedia(at: i) if let inf { mediaList.adding(inf) } @@ -685,7 +681,7 @@ class RCTVideo: UIView, RCTVideoPlayerViewControllerDelegate, RCTPlayerObserverH func getVideoTrackInfo( model: M3U8PlaylistModel, - masterModel: M3U8PlaylistModel + principalModel: M3U8PlaylistModel ) -> [String: Any] { if !model.mainMediaPl.segmentList.isEmpty { let uri: URL = model.mainMediaPl.segmentList.segmentInfo(at: 0).uri @@ -695,8 +691,8 @@ class RCTVideo: UIView, RCTVideoPlayerViewControllerDelegate, RCTPlayerObserverH var codecs = "" - if !masterModel.masterPlaylist.xStreamList.isEmpty { - if let inf = masterModel.masterPlaylist.xStreamList.xStreamInf(at: 0) { + if !principalModel.masterPlaylist.xStreamList.isEmpty { + if let inf = principalModel.masterPlaylist.xStreamList.xStreamInf(at: 0) { codecs = (inf.codecs as NSArray).componentsJoined(by: ",") } } @@ -842,12 +838,12 @@ class RCTVideo: UIView, RCTVideoPlayerViewControllerDelegate, RCTPlayerObserverH @objc func setSeek(_ info: NSDictionary!) { if self.isManaged() { - if self.isSlave() { + if self.isPeripheral() { return } - let slave = self.slave() + let peripheral = self.peripheral() self.setSeekManaged(info: info) - slave?.setSeekManaged(info: info) + peripheral?.setSeekManaged(info: info) return } let seekTime: NSNumber! = info["time"] as! NSNumber @@ -1555,17 +1551,17 @@ class RCTVideo: UIView, RCTVideoPlayerViewControllerDelegate, RCTPlayerObserverH let url = NSURL(string: urlString) let asset: AVAsset? = _player?.currentItem?.asset - let masterURL: NSURL? = (_player?.currentItem?.asset as? AVURLAsset)?.url as? NSURL + let principalURL: NSURL? = (_player?.currentItem?.asset as? AVURLAsset)?.url as? NSURL - masterURL?.m3u_loadAsyncCompletion { masterModel, _ in + principalURL?.m3u_loadAsyncCompletion { principalModel, _ in if let url { url.m3u_loadAsyncCompletion { model, _ in - if let model, let masterModel { + if let model, let principalModel { self.onPlayedTracksChange?( [ - "audioTrack": self.getAudioTrackInfo(model: model, masterModel: masterModel), + "audioTrack": self.getAudioTrackInfo(model: model, principalModel: principalModel), "textTrack": self._textTracks, - "videoTrack": self.getVideoTrackInfo(model: model, masterModel: masterModel), + "videoTrack": self.getVideoTrackInfo(model: model, principalModel: principalModel), ] ) } @@ -1606,10 +1602,10 @@ class RCTVideo: UIView, RCTVideoPlayerViewControllerDelegate, RCTPlayerObserverH // Continue playing (or not if paused) after being paused due to hitting an unbuffered zone. func handlePlaybackLikelyToKeepUp(playerItem _: AVPlayerItem, change _: NSKeyValueObservedChange<Bool>) { if self.isManaged() { - if self.isSlave() { - self.onSlaveVideoStatusChange() + if self.isPeripheral() { + self.onPeripheralVideoStatusChange() } else { - _masterPendingPlayRequest = true + _principalPendingPlayRequest = true } } else { if (!_controls || _fullscreenPlayerPresented || _isBuffering) && _playerItem?.isPlaybackLikelyToKeepUp ?? false { @@ -1795,72 +1791,72 @@ class RCTVideo: UIView, RCTVideoPlayerViewControllerDelegate, RCTPlayerObserverH @objc func setOnClick(_: Any) {} - func setMasterVideo(tag: NSNumber) { - _masterVideo = tag + func setPrincipalVideo(tag: NSNumber) { + _principalVideo = tag } - func setSlaveVideo(tag: NSNumber) { - _slaveVideo = tag + func setPeripheralVideo(tag: NSNumber) { + _peripheralVideo = tag } - func isMaster() -> Bool { - _slaveVideo != nil + func isPrincipal() -> Bool { + _peripheralVideo != nil } - func isSlave() -> Bool { - _masterVideo != nil + func isPeripheral() -> Bool { + _principalVideo != nil } - func slave() -> RCTVideo? { - guard let video = _slaveVideo else { + func peripheral() -> RCTVideo? { + guard let video = _peripheralVideo else { return nil } return CurrentVideos.shared().video(forTag: video) } - func master() -> RCTVideo? { - guard let video = _masterVideo else { + func principal() -> RCTVideo? { + guard let video = _principalVideo else { return nil } return CurrentVideos.shared().video(forTag: video) } func isManaged() -> Bool { - isSlave() || isMaster() + isPeripheral() || isPrincipal() } func setManagedPaused(paused: Bool) { - if isSlave() { + if isPeripheral() { return } - guard let slave = slave() else { + guard let peripheral = peripheral() else { return } - var slavePlayer = slave._player + var peripheralPlayer = peripheral._player if paused { _player?.pause() - slavePlayer?.pause() + peripheralPlayer?.pause() _player?.rate = 0.0 - slavePlayer?.rate = 0.0 + peripheralPlayer?.rate = 0.0 } else { - if !isVideoReady() || slave.isVideoReady() { - _masterPendingPlayRequest = true + if !isVideoReady() || peripheral.isVideoReady() { + _principalPendingPlayRequest = true return } RCTPlayerOperations.configureAudio(ignoreSilentSwitch: _ignoreSilentSwitch, mixWithOthers: _mixWithOthers, audioOutput: _audioOutput) if #available(iOS 10.0, *), !_automaticallyWaitsToMinimizeStalling { _player?.playImmediately(atRate: _rate) - slavePlayer?.playImmediately(atRate: _rate) + peripheralPlayer?.playImmediately(atRate: _rate) } else { _player?.play() - slavePlayer?.play() + peripheralPlayer?.play() } _player?.rate = _rate - slavePlayer?.rate = _rate + peripheralPlayer?.rate = _rate } _paused = paused - slave.setRaw(paused: paused) + peripheral.setRaw(paused: paused) } func isVideoReady() -> Bool { @@ -1876,10 +1872,10 @@ class RCTVideo: UIView, RCTVideoPlayerViewControllerDelegate, RCTPlayerObserverH } func setSeekManaged(info: NSDictionary) { - if !isMaster() { + if !isPrincipal() { return } - guard let slave = slave() else { + guard let peripheral = peripheral() else { return } let seekTime: NSNumber? = info["time"] as? NSNumber @@ -1888,9 +1884,9 @@ class RCTVideo: UIView, RCTVideoPlayerViewControllerDelegate, RCTPlayerObserverH let timeScale: Int32 = 1000 let item: AVPlayerItem? = _player?.currentItem - let slavePlayer: AVPlayer? = slave.player() - let slaveItem: AVPlayerItem? = slavePlayer?.currentItem - if let item, item.status == .readyToPlay, let slaveItem, slaveItem.status == .readyToPlay { + let peripheralPlayer: AVPlayer? = peripheral.player() + let peripheralItem: AVPlayerItem? = peripheralPlayer?.currentItem + if let item, item.status == .readyToPlay, let peripheralItem, peripheralItem.status == .readyToPlay { // TODO: check loadedTimeRanges let cmSeekTime: CMTime = CMTimeMakeWithSeconds(Float64(seekTime?.floatValue ?? .zero), preferredTimescale: timeScale) @@ -1900,7 +1896,7 @@ class RCTVideo: UIView, RCTVideoPlayerViewControllerDelegate, RCTPlayerObserverH if CMTimeCompare(current, cmSeekTime) != 0 { let wasPaused = _paused _player?.pause() - slavePlayer?.pause() + peripheralPlayer?.pause() let seekGroup: DispatchGroup = .init() seekGroup.enter() @@ -1908,13 +1904,13 @@ class RCTVideo: UIView, RCTVideoPlayerViewControllerDelegate, RCTPlayerObserverH seekGroup.leave() }) seekGroup.enter() - slavePlayer?.seek(to: cmSeekTime, toleranceBefore: tolerance, toleranceAfter: tolerance, completionHandler: { _ in + peripheralPlayer?.seek(to: cmSeekTime, toleranceBefore: tolerance, toleranceAfter: tolerance, completionHandler: { _ in seekGroup.leave() }) seekGroup.notify(queue: .main) { self.seekCompletedFor(seekTime: seekTime ?? 0) - slave.seekCompletedFor(seekTime: seekTime ?? 0) + peripheral.seekCompletedFor(seekTime: seekTime ?? 0) DispatchQueue.main.asyncAfter(deadline: .now() + 1) { self.setManagedPaused(paused: wasPaused) } @@ -1946,23 +1942,23 @@ class RCTVideo: UIView, RCTVideoPlayerViewControllerDelegate, RCTPlayerObserverH return } _videoState != state - if self.isSlave() { - let master = self.master() - master?.onSlaveVideoStatusChange() + if self.isPeripheral() { + let principal = self.principal() + principal?.onPeripheralVideoStatusChange() } } - func onSlaveVideoStatusChange() { - let slave = self.slave() - if slave != nil { + func onPeripheralVideoStatusChange() { + let peripheral = self.peripheral() + if peripheral != nil { return } - if slave?.isVideoReady() ?? false && _masterPendingPlayRequest { + if peripheral?.isVideoReady() ?? false && _principalPendingPlayRequest { self.setPaused(false) - _masterPendingPlayRequest = false - } else if self.isVideoReady() && self.slave()?.isVideoReady() ?? false { + _principalPendingPlayRequest = false + } else if self.isVideoReady() && self.peripheral()?.isVideoReady() ?? false { self.setPaused(false) - _masterPendingPlayRequest = false + _principalPendingPlayRequest = false } } } diff --git a/ios/Video/RCTVideoManager.swift b/ios/Video/RCTVideoManager.swift index 3d4595b960..5e34a684d4 100644 --- a/ios/Video/RCTVideoManager.swift +++ b/ios/Video/RCTVideoManager.swift @@ -62,7 +62,7 @@ class RCTVideoManager: RCTViewManager { videoView?.dismissFullscreenPlayer() }) } - + @objc(getCurrentTime:commandId:) func getCurrentTime(_ reactTag: NSNumber, commandId: NSNumber) { performOnVideoView(withReactTag: reactTag, callback: { videoView in From facf5581a68646b6fc1515fc11e86fe764aa66a6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pawel=20Lozi=C5=84ski?= <pawel.lozinski@miquido.com> Date: Thu, 13 Jun 2024 23:32:38 +0200 Subject: [PATCH 18/37] Update names in Video.tsx --- src/Video.tsx | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/src/Video.tsx b/src/Video.tsx index 80b1e95e2f..8a52c99a67 100644 --- a/src/Video.tsx +++ b/src/Video.tsx @@ -66,8 +66,8 @@ export interface VideoRef { save: (options: object) => Promise<VideoSaveData>; setVolume: (volume: number) => void; getCurrentTime: () => Promise<number>; - setMasterVideoId: () => void; - setSlaveVideoId: () => void; + setPrincipalVideoId: () => void; + setPeripheralVideoId: () => void; } const Video = forwardRef<VideoRef, ReactVideoProps>( @@ -80,8 +80,8 @@ const Video = forwardRef<VideoRef, ReactVideoProps>( poster, fullscreen, drm, - masterVideo, - slaveVideo, + principalVideo, + peripheralVideo, textTracks, selectedVideoTrack, selectedAudioTrack, @@ -304,12 +304,12 @@ const Video = forwardRef<VideoRef, ReactVideoProps>( return VideoManager.setVolume(volume, getReactTag(nativeRef)); }, []); - const setMasterVideoId = useCallback((masterId: string) => { - nativeRef.current?.setNativeProps({masterVideo: masterId}); + const setPrincipalVideoId = useCallback((principalId: string) => { + nativeRef.current?.setNativeProps({principalVideo: principalId}); }, []); - const setSlaveVideoId = useCallback((slaveId: string) => { - nativeRef.current?.setNativeProps({slaveVideo: slaveId}); + const setPeripheralVideoId = useCallback((peripheralId: string) => { + nativeRef.current?.setNativeProps({peripheralVideo: peripheralId}); }, []); const onVideoLoadStart = useCallback( @@ -551,6 +551,8 @@ const Video = forwardRef<VideoRef, ReactVideoProps>( {...rest} src={src} drm={_drm} + principalVideo={principalVideo} + peripheralVideo={peripheralVideo} style={StyleSheet.absoluteFill} resizeMode={resizeMode} fullscreen={isFullscreen} From d60409333b9183deda04523efb33fb152234a141 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pawel=20Lozi=C5=84ski?= <pawel.lozinski@miquido.com> Date: Thu, 13 Jun 2024 23:32:50 +0200 Subject: [PATCH 19/37] sh scripts/clang-format.sh --- ios/Video/CurrentVideos.h | 4 +- ios/Video/CurrentVideos.m | 47 +- ios/Video/M3U8Kit/Source/M3U8ExtXByteRange.h | 40 +- ios/Video/M3U8Kit/Source/M3U8ExtXByteRange.m | 64 +-- ios/Video/M3U8Kit/Source/M3U8ExtXKey.h | 44 +- ios/Video/M3U8Kit/Source/M3U8ExtXKey.m | 68 +-- ios/Video/M3U8Kit/Source/M3U8ExtXMedia.h | 93 ++-- ios/Video/M3U8Kit/Source/M3U8ExtXMedia.m | 172 +++--- ios/Video/M3U8Kit/Source/M3U8ExtXMediaList.h | 58 +- ios/Video/M3U8Kit/Source/M3U8ExtXMediaList.m | 223 ++++---- ios/Video/M3U8Kit/Source/M3U8ExtXStreamInf.h | 88 ++-- ios/Video/M3U8Kit/Source/M3U8ExtXStreamInf.m | 194 ++++--- .../M3U8Kit/Source/M3U8ExtXStreamInfList.h | 46 +- .../M3U8Kit/Source/M3U8ExtXStreamInfList.m | 104 ++-- ios/Video/M3U8Kit/Source/M3U8LineReader.h | 42 +- ios/Video/M3U8Kit/Source/M3U8LineReader.m | 65 ++- ios/Video/M3U8Kit/Source/M3U8MasterPlaylist.h | 64 +-- ios/Video/M3U8Kit/Source/M3U8MasterPlaylist.m | 360 ++++++------- ios/Video/M3U8Kit/Source/M3U8MediaPlaylist.h | 66 +-- ios/Video/M3U8Kit/Source/M3U8MediaPlaylist.m | 266 +++++----- ios/Video/M3U8Kit/Source/M3U8Parser.h | 44 +- ios/Video/M3U8Kit/Source/M3U8PlaylistModel.h | 93 ++-- ios/Video/M3U8Kit/Source/M3U8PlaylistModel.m | 494 +++++++++--------- ios/Video/M3U8Kit/Source/M3U8SegmentInfo.h | 60 ++- ios/Video/M3U8Kit/Source/M3U8SegmentInfo.m | 112 ++-- .../M3U8Kit/Source/M3U8SegmentInfoList.h | 42 +- .../M3U8Kit/Source/M3U8SegmentInfoList.m | 62 +-- .../M3U8Kit/Source/M3U8TagsAndAttributes.h | 283 +++++----- ios/Video/M3U8Kit/Source/NSArray+m3u8.h | 40 +- ios/Video/M3U8Kit/Source/NSArray+m3u8.m | 84 +-- ios/Video/M3U8Kit/Source/NSString+m3u8.h | 44 +- ios/Video/M3U8Kit/Source/NSString+m3u8.m | 235 +++++---- ios/Video/M3U8Kit/Source/NSURL+m3u8.h | 40 +- ios/Video/M3U8Kit/Source/NSURL+m3u8.m | 92 ++-- ios/Video/RCTVideo-Bridging-Header.h | 6 +- ios/Video/RCTVideoManager.m | 4 +- ios/Video/WeakVideoRef.h | 2 +- 37 files changed, 1881 insertions(+), 1964 deletions(-) diff --git a/ios/Video/CurrentVideos.h b/ios/Video/CurrentVideos.h index 769b2bf8e4..887ee158e9 100644 --- a/ios/Video/CurrentVideos.h +++ b/ios/Video/CurrentVideos.h @@ -12,8 +12,8 @@ NS_ASSUME_NONNULL_BEGIN @class RCTVideo; @interface CurrentVideos : NSObject + (instancetype)shared; -- (void)add: (RCTVideo*)video forTag: (NSNumber*)tag; -- (nullable RCTVideo*)videoForTag: (NSNumber*)tag; +- (void)add:(RCTVideo*)video forTag:(NSNumber*)tag; +- (nullable RCTVideo*)videoForTag:(NSNumber*)tag; @end NS_ASSUME_NONNULL_END diff --git a/ios/Video/CurrentVideos.m b/ios/Video/CurrentVideos.m index 1481e3d253..6f9d7856ed 100644 --- a/ios/Video/CurrentVideos.m +++ b/ios/Video/CurrentVideos.m @@ -9,47 +9,46 @@ #import "WeakVideoRef.h" @interface CurrentVideos () -@property (strong) NSMutableDictionary <NSNumber*, WeakVideoRef*> *videos; +@property(strong) NSMutableDictionary<NSNumber*, WeakVideoRef*>* videos; @end @implementation CurrentVideos + (nonnull instancetype)shared { - static CurrentVideos *instance = nil; - static dispatch_once_t onceToken; - dispatch_once(&onceToken, ^{ - instance = [[self alloc] init]; - }); - return instance; + static CurrentVideos* instance = nil; + static dispatch_once_t onceToken; + dispatch_once(&onceToken, ^{ + instance = [[self alloc] init]; + }); + return instance; } - (instancetype)init { - if (self = [super init]) { - _videos = [[NSMutableDictionary alloc] init]; - } + if (self = [super init]) { + _videos = [[NSMutableDictionary alloc] init]; + } - return self; + return self; } -- (void)add:(nonnull RCTVideo *)video forTag:(nonnull NSNumber *)tag { - [self cleanup]; - WeakVideoRef *ref = [[WeakVideoRef alloc] init]; - ref.video = video; - [_videos setObject:ref forKey:tag]; +- (void)add:(nonnull RCTVideo*)video forTag:(nonnull NSNumber*)tag { + [self cleanup]; + WeakVideoRef* ref = [[WeakVideoRef alloc] init]; + ref.video = video; + [_videos setObject:ref forKey:tag]; } - -- (nullable RCTVideo *)videoForTag:(nonnull NSNumber *)tag { - [self cleanup]; - return [[_videos objectForKey:tag] video]; +- (nullable RCTVideo*)videoForTag:(nonnull NSNumber*)tag { + [self cleanup]; + return [[_videos objectForKey:tag] video]; } - (void)cleanup { - for (NSNumber* key in [_videos allKeys]) { - if (_videos[key].video == nil) { - [_videos removeObjectForKey:key]; - } + for (NSNumber* key in [_videos allKeys]) { + if (_videos[key].video == nil) { + [_videos removeObjectForKey:key]; } + } } @end diff --git a/ios/Video/M3U8Kit/Source/M3U8ExtXByteRange.h b/ios/Video/M3U8Kit/Source/M3U8ExtXByteRange.h index 66479ebc73..dfe451e4f9 100644 --- a/ios/Video/M3U8Kit/Source/M3U8ExtXByteRange.h +++ b/ios/Video/M3U8Kit/Source/M3U8ExtXByteRange.h @@ -5,27 +5,27 @@ // Created by Frank on 2020/10/1. // Copyright © 2020 M3U8Kit. All rights reserved. // -//The MIT License (MIT) +// The MIT License (MIT) // -//Copyright (c) 2015 Sun Jin <jeansunvf@gmail.com> +// Copyright (c) 2015 Sun Jin <jeansunvf@gmail.com> // -//Permission is hereby granted, free of charge, to any person obtaining a copy -//of this software and associated documentation files (the "Software"), to deal -//in the Software without restriction, including without limitation the rights -//to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -//copies of the Software, and to permit persons to whom the Software is -//furnished to do so, subject to the following conditions: +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: // -//The above copyright notice and this permission notice shall be included in all -//copies or substantial portions of the Software. +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. // -//THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -//IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -//FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -//AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -//LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -//OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -//SOFTWARE. +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. #import <Foundation/Foundation.h> @@ -33,11 +33,11 @@ NS_ASSUME_NONNULL_BEGIN @interface M3U8ExtXByteRange : NSObject -- (instancetype)initWithAtString:(NSString *)atString; +- (instancetype)initWithAtString:(NSString*)atString; - (instancetype)initWithLength:(NSInteger)length offset:(NSInteger)offset; -@property (nonatomic, assign, readonly) NSInteger length; -@property (nonatomic, assign, readonly) NSInteger offset; +@property(nonatomic, assign, readonly) NSInteger length; +@property(nonatomic, assign, readonly) NSInteger offset; @end diff --git a/ios/Video/M3U8Kit/Source/M3U8ExtXByteRange.m b/ios/Video/M3U8Kit/Source/M3U8ExtXByteRange.m index 4008c80740..cf28e99122 100644 --- a/ios/Video/M3U8Kit/Source/M3U8ExtXByteRange.m +++ b/ios/Video/M3U8Kit/Source/M3U8ExtXByteRange.m @@ -5,50 +5,50 @@ // Created by Frank on 2020/10/1. // Copyright © 2020 M3U8Kit. All rights reserved. // -//The MIT License (MIT) +// The MIT License (MIT) // -//Copyright (c) 2015 Sun Jin <jeansunvf@gmail.com> +// Copyright (c) 2015 Sun Jin <jeansunvf@gmail.com> // -//Permission is hereby granted, free of charge, to any person obtaining a copy -//of this software and associated documentation files (the "Software"), to deal -//in the Software without restriction, including without limitation the rights -//to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -//copies of the Software, and to permit persons to whom the Software is -//furnished to do so, subject to the following conditions: +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: // -//The above copyright notice and this permission notice shall be included in all -//copies or substantial portions of the Software. +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. // -//THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -//IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -//FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -//AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -//LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -//OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -//SOFTWARE. +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. #import "M3U8ExtXByteRange.h" @implementation M3U8ExtXByteRange -- (instancetype)initWithAtString:(NSString *)atString { - NSArray<NSString *> *params = [atString componentsSeparatedByString:@"@"]; - NSInteger length = params.firstObject.integerValue; - NSInteger offset = 0; - if (params.count > 1) { - offset = MAX(0, params[1].integerValue); - } - - return [self initWithLength:length offset:offset]; +- (instancetype)initWithAtString:(NSString*)atString { + NSArray<NSString*>* params = [atString componentsSeparatedByString:@"@"]; + NSInteger length = params.firstObject.integerValue; + NSInteger offset = 0; + if (params.count > 1) { + offset = MAX(0, params[1].integerValue); + } + + return [self initWithLength:length offset:offset]; } - (instancetype)initWithLength:(NSInteger)length offset:(NSInteger)offset { - self = [super init]; - if (self) { - _length = length; - _offset = offset; - } - return self; + self = [super init]; + if (self) { + _length = length; + _offset = offset; + } + return self; } @end diff --git a/ios/Video/M3U8Kit/Source/M3U8ExtXKey.h b/ios/Video/M3U8Kit/Source/M3U8ExtXKey.h index 69293e7150..1be573e24e 100644 --- a/ios/Video/M3U8Kit/Source/M3U8ExtXKey.h +++ b/ios/Video/M3U8Kit/Source/M3U8ExtXKey.h @@ -5,36 +5,36 @@ // Created by Pierre Perrin on 01/02/2019. // Copyright © 2019 M3U8Kit. All rights reserved. // -//The MIT License (MIT) +// The MIT License (MIT) // -//Copyright (c) 2015 Sun Jin <jeansunvf@gmail.com> +// Copyright (c) 2015 Sun Jin <jeansunvf@gmail.com> // -//Permission is hereby granted, free of charge, to any person obtaining a copy -//of this software and associated documentation files (the "Software"), to deal -//in the Software without restriction, including without limitation the rights -//to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -//copies of the Software, and to permit persons to whom the Software is -//furnished to do so, subject to the following conditions: +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: // -//The above copyright notice and this permission notice shall be included in all -//copies or substantial portions of the Software. +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. // -//THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -//IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -//FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -//AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -//LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -//OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -//SOFTWARE. +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. #import <Foundation/Foundation.h> @interface M3U8ExtXKey : NSObject -- (instancetype)initWithDictionary:(NSDictionary *)dictionary; +- (instancetype)initWithDictionary:(NSDictionary*)dictionary; -- (NSString *)method; -- (NSString *)url; -- (NSString *)keyFormat; -- (NSString *)iV; +- (NSString*)method; +- (NSString*)url; +- (NSString*)keyFormat; +- (NSString*)iV; @end diff --git a/ios/Video/M3U8Kit/Source/M3U8ExtXKey.m b/ios/Video/M3U8Kit/Source/M3U8ExtXKey.m index 47a433b041..42da2e5979 100644 --- a/ios/Video/M3U8Kit/Source/M3U8ExtXKey.m +++ b/ios/Video/M3U8Kit/Source/M3U8ExtXKey.m @@ -5,62 +5,62 @@ // Created by Pierre Perrin on 01/02/2019. // Copyright © 2019 M3U8Kit. All rights reserved. // -//The MIT License (MIT) +// The MIT License (MIT) // -//Copyright (c) 2015 Sun Jin <jeansunvf@gmail.com> +// Copyright (c) 2015 Sun Jin <jeansunvf@gmail.com> // -//Permission is hereby granted, free of charge, to any person obtaining a copy -//of this software and associated documentation files (the "Software"), to deal -//in the Software without restriction, including without limitation the rights -//to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -//copies of the Software, and to permit persons to whom the Software is -//furnished to do so, subject to the following conditions: +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: // -//The above copyright notice and this permission notice shall be included in all -//copies or substantial portions of the Software. +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. // -//THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -//IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -//FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -//AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -//LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -//OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -//SOFTWARE. +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. #import "M3U8ExtXKey.h" #import "M3U8TagsAndAttributes.h" -@interface M3U8ExtXKey() -@property (nonatomic, strong) NSDictionary *dictionary; +@interface M3U8ExtXKey () +@property(nonatomic, strong) NSDictionary* dictionary; @end @implementation M3U8ExtXKey -- (instancetype)initWithDictionary:(NSDictionary *)dictionary { - if (self = [super init]) { - self.dictionary = dictionary; - } - return self; +- (instancetype)initWithDictionary:(NSDictionary*)dictionary { + if (self = [super init]) { + self.dictionary = dictionary; + } + return self; } -- (NSString *)method { - return self.dictionary[M3U8_EXT_X_KEY_METHOD]; +- (NSString*)method { + return self.dictionary[M3U8_EXT_X_KEY_METHOD]; } -- (NSString *)url { - return self.dictionary[M3U8_EXT_X_KEY_URI]; +- (NSString*)url { + return self.dictionary[M3U8_EXT_X_KEY_URI]; } -- (NSString *)keyFormat { - return self.dictionary[M3U8_EXT_X_KEY_KEYFORMAT]; +- (NSString*)keyFormat { + return self.dictionary[M3U8_EXT_X_KEY_KEYFORMAT]; } -- (NSString *)iV { - return self.dictionary[M3U8_EXT_X_KEY_IV]; +- (NSString*)iV { + return self.dictionary[M3U8_EXT_X_KEY_IV]; } -- (NSString *)description { - return self.dictionary.description; +- (NSString*)description { + return self.dictionary.description; } @end diff --git a/ios/Video/M3U8Kit/Source/M3U8ExtXMedia.h b/ios/Video/M3U8Kit/Source/M3U8ExtXMedia.h index f5c13cfede..80633fe174 100644 --- a/ios/Video/M3U8Kit/Source/M3U8ExtXMedia.h +++ b/ios/Video/M3U8Kit/Source/M3U8ExtXMedia.h @@ -5,72 +5,75 @@ // Created by Sun Jin on 3/25/14. // Copyright (c) 2014 Jin Sun. All rights reserved. // -//The MIT License (MIT) +// The MIT License (MIT) // -//Copyright (c) 2015 Sun Jin <jeansunvf@gmail.com> +// Copyright (c) 2015 Sun Jin <jeansunvf@gmail.com> // -//Permission is hereby granted, free of charge, to any person obtaining a copy -//of this software and associated documentation files (the "Software"), to deal -//in the Software without restriction, including without limitation the rights -//to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -//copies of the Software, and to permit persons to whom the Software is -//furnished to do so, subject to the following conditions: +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: // -//The above copyright notice and this permission notice shall be included in all -//copies or substantial portions of the Software. +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. // -//THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -//IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -//FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -//AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -//LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -//OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -//SOFTWARE. +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. #import <Foundation/Foundation.h> /* - + /// EXT-X-MEDIA - + @format #EXT-X-MEDIA:<attribute-list> , attibute-list: ATTR=<value>,... - @example #EXT-X-MEDIA:TYPE=AUDIO,GROUP-ID="600k",LANGUAGE="eng",NAME="Audio",AUTOSELECT=YES,DEFAULT=YES,URI="/talks/769/audio/600k.m3u8?sponsor=Ripple",BANDWIDTH=614400 - + @example +#EXT-X-MEDIA:TYPE=AUDIO,GROUP-ID="600k",LANGUAGE="eng",NAME="Audio",AUTOSELECT=YES,DEFAULT=YES,URI="/talks/769/audio/600k.m3u8?sponsor=Ripple",BANDWIDTH=614400 + #define M3U8_EXT_X_MEDIA @"#EXT-X-MEDIA:" // EXT-X-MEDIA attributes -#define M3U8_EXT_X_MEDIA_TYPE @"TYPE" // The value is enumerated-string; valid strings are AUDIO, VIDEO, SUBTITLES and CLOSED-CAPTIONS. -#define M3U8_EXT_X_MEDIA_URI @"URI" // The value is a quoted-string containing a URI that identifies the Playlist file. -#define M3U8_EXT_X_MEDIA_GROUP_ID @"GROUP-ID" // The value is a quoted-string identifying a mutually-exclusive group of renditions. -#define M3U8_EXT_X_MEDIA_LANGUAGE @"LANGUAGE" // The value is a quoted-string containing an RFC 5646 [RFC5646] language tag that identifies the primary language used in the rendition. -#define M3U8_EXT_X_MEDIA_ASSOC_LANGUAGE @"ASSOC-LANGUAGE" // The value is a quoted-string containing an RFC 5646 [RFC5646](http://tools.ietf.org/html/rfc5646) language tag that identifies a language that is associated with the rendition. -#define M3U8_EXT_X_MEDIA_NAME @"NAME" // The value is a quoted-string containing a human-readable description of the rendition. -#define M3U8_EXT_X_MEDIA_DEFAULT @"DEFAULT" // The value is an enumerated-string; valid strings are YES and NO. -#define M3U8_EXT_X_MEDIA_AUTOSELECT @"AUTOSELECT" // The value is an enumerated-string; valid strings are YES and NO. -#define M3U8_EXT_X_MEDIA_FORCED @"FORCED" // The value is an enumerated-string; valid strings are YES and NO. -#define M3U8_EXT_X_MEDIA_INSTREAM_ID @"INSTREAM-ID" // The value is a quoted-string that specifies a rendition within the segments in the Media Playlist. -#define M3U8_EXT_X_MEDIA_CHARACTERISTICS @"CHARACTERISTICS" // The value is a quoted-string containing one or more Uniform Type Identifiers [UTI] separated by comma (,) characters. - +#define M3U8_EXT_X_MEDIA_TYPE @"TYPE" // The value is enumerated-string; valid strings are AUDIO, VIDEO, SUBTITLES and +CLOSED-CAPTIONS. #define M3U8_EXT_X_MEDIA_URI @"URI" // The value is a quoted-string containing a URI that identifies the +Playlist file. #define M3U8_EXT_X_MEDIA_GROUP_ID @"GROUP-ID" // The value is a quoted-string identifying a mutually-exclusive +group of renditions. #define M3U8_EXT_X_MEDIA_LANGUAGE @"LANGUAGE" // The value is a quoted-string containing an RFC 5646 +[RFC5646] language tag that identifies the primary language used in the rendition. #define M3U8_EXT_X_MEDIA_ASSOC_LANGUAGE @"ASSOC-LANGUAGE" +// The value is a quoted-string containing an RFC 5646 [RFC5646](http://tools.ietf.org/html/rfc5646) language tag that identifies a language +that is associated with the rendition. #define M3U8_EXT_X_MEDIA_NAME @"NAME" // The value is a quoted-string containing a +human-readable description of the rendition. #define M3U8_EXT_X_MEDIA_DEFAULT @"DEFAULT" // The value is an enumerated-string; +valid strings are YES and NO. #define M3U8_EXT_X_MEDIA_AUTOSELECT @"AUTOSELECT" // The value is an enumerated-string; valid strings +are YES and NO. #define M3U8_EXT_X_MEDIA_FORCED @"FORCED" // The value is an enumerated-string; valid strings are YES and NO. +#define M3U8_EXT_X_MEDIA_INSTREAM_ID @"INSTREAM-ID" // The value is a quoted-string that specifies a rendition within the segments in +the Media Playlist. #define M3U8_EXT_X_MEDIA_CHARACTERISTICS @"CHARACTERISTICS" // The value is a quoted-string containing one or more +Uniform Type Identifiers [UTI] separated by comma (,) characters. + */ @interface M3U8ExtXMedia : NSObject -- (instancetype)initWithDictionary:(NSDictionary *)dictionary; +- (instancetype)initWithDictionary:(NSDictionary*)dictionary; -- (NSString *)type; -- (NSURL *)URI; -- (NSString *)groupId; -- (NSString *)channels; -- (NSString *)language; -- (NSString *)assocLanguage; -- (NSString *)name; +- (NSString*)type; +- (NSURL*)URI; +- (NSString*)groupId; +- (NSString*)channels; +- (NSString*)language; +- (NSString*)assocLanguage; +- (NSString*)name; - (BOOL)isDefault; - (BOOL)autoSelect; - (BOOL)forced; -- (NSString *)instreamId; -- (NSString *)characteristics; +- (NSString*)instreamId; +- (NSString*)characteristics; - (NSInteger)bandwidth; -- (NSURL *)m3u8URL; // the absolute url of media playlist file -- (NSString *)m3u8PlainString; +- (NSURL*)m3u8URL; // the absolute url of media playlist file +- (NSString*)m3u8PlainString; @end diff --git a/ios/Video/M3U8Kit/Source/M3U8ExtXMedia.m b/ios/Video/M3U8Kit/Source/M3U8ExtXMedia.m index 2371b38b72..79fda34c18 100644 --- a/ios/Video/M3U8Kit/Source/M3U8ExtXMedia.m +++ b/ios/Video/M3U8Kit/Source/M3U8ExtXMedia.m @@ -5,154 +5,146 @@ // Created by Sun Jin on 3/25/14. // Copyright (c) 2014 Jin Sun. All rights reserved. // -//The MIT License (MIT) +// The MIT License (MIT) // -//Copyright (c) 2015 Sun Jin <jeansunvf@gmail.com> +// Copyright (c) 2015 Sun Jin <jeansunvf@gmail.com> // -//Permission is hereby granted, free of charge, to any person obtaining a copy -//of this software and associated documentation files (the "Software"), to deal -//in the Software without restriction, including without limitation the rights -//to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -//copies of the Software, and to permit persons to whom the Software is -//furnished to do so, subject to the following conditions: +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: // -//The above copyright notice and this permission notice shall be included in all -//copies or substantial portions of the Software. +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. // -//THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -//IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -//FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -//AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -//LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -//OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -//SOFTWARE. +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. #import "M3U8ExtXMedia.h" #import "M3U8TagsAndAttributes.h" -@interface M3U8ExtXMedia() -@property (nonatomic, strong) NSDictionary *dictionary; +@interface M3U8ExtXMedia () +@property(nonatomic, strong) NSDictionary* dictionary; @end @implementation M3U8ExtXMedia -- (instancetype)initWithDictionary:(NSDictionary *)dictionary { - if (self = [super init]) { - self.dictionary = dictionary; - } - return self; +- (instancetype)initWithDictionary:(NSDictionary*)dictionary { + if (self = [super init]) { + self.dictionary = dictionary; + } + return self; } -- (NSURL *)baseURL { - return self.dictionary[M3U8_BASE_URL]; +- (NSURL*)baseURL { + return self.dictionary[M3U8_BASE_URL]; } -- (NSURL *)URL { - return self.dictionary[M3U8_URL]; +- (NSURL*)URL { + return self.dictionary[M3U8_URL]; } -- (NSString *)type { - return self.dictionary[M3U8_EXT_X_MEDIA_TYPE]; +- (NSString*)type { + return self.dictionary[M3U8_EXT_X_MEDIA_TYPE]; } -- (NSURL *)URI { - NSString *originalUrl = self.dictionary[M3U8_EXT_X_MEDIA_URI]; - NSString *urlString = [originalUrl stringByReplacingOccurrencesOfString:@" " withString:@"%20"]; - return [NSURL URLWithString:urlString]; +- (NSURL*)URI { + NSString* originalUrl = self.dictionary[M3U8_EXT_X_MEDIA_URI]; + NSString* urlString = [originalUrl stringByReplacingOccurrencesOfString:@" " withString:@"%20"]; + return [NSURL URLWithString:urlString]; } -- (NSString *)groupId { - return self.dictionary[M3U8_EXT_X_MEDIA_GROUP_ID]; +- (NSString*)groupId { + return self.dictionary[M3U8_EXT_X_MEDIA_GROUP_ID]; } -- (NSString *)channels { - return self.dictionary[M3U8_EXT_X_MEDIA_CHANNELS]; +- (NSString*)channels { + return self.dictionary[M3U8_EXT_X_MEDIA_CHANNELS]; } -- (NSString *)language { - return [self.dictionary[M3U8_EXT_X_MEDIA_LANGUAGE] lowercaseString]; +- (NSString*)language { + return [self.dictionary[M3U8_EXT_X_MEDIA_LANGUAGE] lowercaseString]; } -- (NSString *)assocLanguage { - return self.dictionary[M3U8_EXT_X_MEDIA_ASSOC_LANGUAGE]; +- (NSString*)assocLanguage { + return self.dictionary[M3U8_EXT_X_MEDIA_ASSOC_LANGUAGE]; } -- (NSString *)name { - return self.dictionary[M3U8_EXT_X_MEDIA_NAME]; +- (NSString*)name { + return self.dictionary[M3U8_EXT_X_MEDIA_NAME]; } - (BOOL)isDefault { - return [self.dictionary[M3U8_EXT_X_MEDIA_DEFAULT] boolValue]; + return [self.dictionary[M3U8_EXT_X_MEDIA_DEFAULT] boolValue]; } - (BOOL)autoSelect { - return [self.dictionary[M3U8_EXT_X_MEDIA_AUTOSELECT] boolValue]; + return [self.dictionary[M3U8_EXT_X_MEDIA_AUTOSELECT] boolValue]; } - (BOOL)forced { - return [self.dictionary[M3U8_EXT_X_MEDIA_FORCED] boolValue]; + return [self.dictionary[M3U8_EXT_X_MEDIA_FORCED] boolValue]; } -- (NSString *)instreamId { - return self.dictionary[M3U8_EXT_X_MEDIA_INSTREAM_ID]; +- (NSString*)instreamId { + return self.dictionary[M3U8_EXT_X_MEDIA_INSTREAM_ID]; } -- (NSString *)characteristics { - return self.dictionary[M3U8_EXT_X_MEDIA_CHARACTERISTICS]; +- (NSString*)characteristics { + return self.dictionary[M3U8_EXT_X_MEDIA_CHARACTERISTICS]; } - (NSInteger)bandwidth { - return [self.dictionary[M3U8_EXT_X_MEDIA_BANDWIDTH] integerValue]; + return [self.dictionary[M3U8_EXT_X_MEDIA_BANDWIDTH] integerValue]; } -- (NSURL *)m3u8URL { - if (self.URI.scheme) { - return self.URI; - } +- (NSURL*)m3u8URL { + if (self.URI.scheme) { + return self.URI; + } - NSString *originalUrl = self.URI.absoluteString; - NSString *urlString = [originalUrl stringByReplacingOccurrencesOfString:@" " withString:@"%20"]; - return [NSURL URLWithString:urlString relativeToURL:[self baseURL]]; + NSString* originalUrl = self.URI.absoluteString; + NSString* urlString = [originalUrl stringByReplacingOccurrencesOfString:@" " withString:@"%20"]; + return [NSURL URLWithString:urlString relativeToURL:[self baseURL]]; } -- (NSString *)description { - return [NSString stringWithString:self.dictionary.description]; +- (NSString*)description { + return [NSString stringWithString:self.dictionary.description]; } /* #EXT-X-MEDIA:TYPE=AUDIO,GROUP-ID="600k",LANGUAGE="eng",NAME="Audio",AUTOSELECT=YES,DEFAULT=YES,URI="main_media_7.m3u8",BANDWIDTH=614400 */ -- (NSString *)m3u8PlainString { - NSMutableString *str = [NSMutableString string]; - [str appendString:M3U8_EXT_X_MEDIA]; - [str appendString:[NSString stringWithFormat:@"TYPE=%@", self.type]]; - [str appendString:[NSString stringWithFormat:@",GROUP-ID=\"%@\"", self.groupId]]; - [str appendString:[NSString stringWithFormat:@",LANGUAGE=\"%@\"", self.language]]; - [str appendString:[NSString stringWithFormat:@",NAME=\"%@\"", self.name]]; - [str appendString:[NSString stringWithFormat:@",AUTOSELECT=%@", self.autoSelect?@"YES":@"NO"]]; - [str appendString:[NSString stringWithFormat:@",DEFAULT=%@", self.isDefault?@"YES":@"NO"]]; - - NSString *fStr = self.dictionary[M3U8_EXT_X_MEDIA_FORCED]; - if (fStr.length > 0) { - [str appendString:[NSString stringWithFormat:@",FORCED=%@", fStr]]; - } - - [str appendString:[NSString stringWithFormat:@",URI=\"%@\"", self.URI.absoluteString]]; - - NSString *bStr = self.dictionary[M3U8_EXT_X_MEDIA_BANDWIDTH]; - if (bStr.length > 0) { - [str appendString:[NSString stringWithFormat:@",BANDWIDTH=%@", bStr]]; - } - - return str; -} - -@end - - - +- (NSString*)m3u8PlainString { + NSMutableString* str = [NSMutableString string]; + [str appendString:M3U8_EXT_X_MEDIA]; + [str appendString:[NSString stringWithFormat:@"TYPE=%@", self.type]]; + [str appendString:[NSString stringWithFormat:@",GROUP-ID=\"%@\"", self.groupId]]; + [str appendString:[NSString stringWithFormat:@",LANGUAGE=\"%@\"", self.language]]; + [str appendString:[NSString stringWithFormat:@",NAME=\"%@\"", self.name]]; + [str appendString:[NSString stringWithFormat:@",AUTOSELECT=%@", self.autoSelect ? @"YES" : @"NO"]]; + [str appendString:[NSString stringWithFormat:@",DEFAULT=%@", self.isDefault ? @"YES" : @"NO"]]; + NSString* fStr = self.dictionary[M3U8_EXT_X_MEDIA_FORCED]; + if (fStr.length > 0) { + [str appendString:[NSString stringWithFormat:@",FORCED=%@", fStr]]; + } + [str appendString:[NSString stringWithFormat:@",URI=\"%@\"", self.URI.absoluteString]]; + NSString* bStr = self.dictionary[M3U8_EXT_X_MEDIA_BANDWIDTH]; + if (bStr.length > 0) { + [str appendString:[NSString stringWithFormat:@",BANDWIDTH=%@", bStr]]; + } + return str; +} +@end diff --git a/ios/Video/M3U8Kit/Source/M3U8ExtXMediaList.h b/ios/Video/M3U8Kit/Source/M3U8ExtXMediaList.h index 8d502f4dd9..0c00f016cb 100644 --- a/ios/Video/M3U8Kit/Source/M3U8ExtXMediaList.h +++ b/ios/Video/M3U8Kit/Source/M3U8ExtXMediaList.h @@ -5,47 +5,47 @@ // Created by Sun Jin on 3/25/14. // Copyright (c) 2014 Jin Sun. All rights reserved. // -//The MIT License (MIT) +// The MIT License (MIT) // -//Copyright (c) 2015 Sun Jin <jeansunvf@gmail.com> +// Copyright (c) 2015 Sun Jin <jeansunvf@gmail.com> // -//Permission is hereby granted, free of charge, to any person obtaining a copy -//of this software and associated documentation files (the "Software"), to deal -//in the Software without restriction, including without limitation the rights -//to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -//copies of the Software, and to permit persons to whom the Software is -//furnished to do so, subject to the following conditions: +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: // -//The above copyright notice and this permission notice shall be included in all -//copies or substantial portions of the Software. +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. // -//THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -//IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -//FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -//AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -//LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -//OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -//SOFTWARE. +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. -#import <Foundation/Foundation.h> #import "M3U8ExtXMedia.h" +#import <Foundation/Foundation.h> @interface M3U8ExtXMediaList : NSObject -@property (nonatomic, assign ,readonly) NSUInteger count; +@property(nonatomic, assign, readonly) NSUInteger count; -- (void)addExtXMedia:(M3U8ExtXMedia *)extXMedia; -- (M3U8ExtXMedia *)xMediaAtIndex:(NSUInteger)index; -- (M3U8ExtXMedia *)firstExtXMedia; -- (M3U8ExtXMedia *)lastExtXMedia; +- (void)addExtXMedia:(M3U8ExtXMedia*)extXMedia; +- (M3U8ExtXMedia*)xMediaAtIndex:(NSUInteger)index; +- (M3U8ExtXMedia*)firstExtXMedia; +- (M3U8ExtXMedia*)lastExtXMedia; -- (M3U8ExtXMediaList *)audioList; -- (M3U8ExtXMedia *)suitableAudio; +- (M3U8ExtXMediaList*)audioList; +- (M3U8ExtXMedia*)suitableAudio; -- (M3U8ExtXMediaList *)videoList; -- (M3U8ExtXMedia *)suitableVideo; +- (M3U8ExtXMediaList*)videoList; +- (M3U8ExtXMedia*)suitableVideo; -- (M3U8ExtXMediaList *)subtitleList; -- (M3U8ExtXMedia *)suitableSubtitle; +- (M3U8ExtXMediaList*)subtitleList; +- (M3U8ExtXMedia*)suitableSubtitle; @end diff --git a/ios/Video/M3U8Kit/Source/M3U8ExtXMediaList.m b/ios/Video/M3U8Kit/Source/M3U8ExtXMediaList.m index be92dcf845..8b9d522754 100644 --- a/ios/Video/M3U8Kit/Source/M3U8ExtXMediaList.m +++ b/ios/Video/M3U8Kit/Source/M3U8ExtXMediaList.m @@ -5,170 +5,157 @@ // Created by Sun Jin on 3/25/14. // Copyright (c) 2014 Jin Sun. All rights reserved. // -//The MIT License (MIT) +// The MIT License (MIT) // -//Copyright (c) 2015 Sun Jin <jeansunvf@gmail.com> +// Copyright (c) 2015 Sun Jin <jeansunvf@gmail.com> // -//Permission is hereby granted, free of charge, to any person obtaining a copy -//of this software and associated documentation files (the "Software"), to deal -//in the Software without restriction, including without limitation the rights -//to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -//copies of the Software, and to permit persons to whom the Software is -//furnished to do so, subject to the following conditions: +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: // -//The above copyright notice and this permission notice shall be included in all -//copies or substantial portions of the Software. +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. // -//THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -//IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -//FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -//AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -//LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -//OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -//SOFTWARE. +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. #import "M3U8ExtXMediaList.h" @interface M3U8ExtXMediaList () -@property (nonatomic, strong) NSMutableArray *m3u8InfoList; +@property(nonatomic, strong) NSMutableArray* m3u8InfoList; @end @implementation M3U8ExtXMediaList - (id)init { - if (self = [super init]) { - self.m3u8InfoList = [NSMutableArray array]; - } - return self; + if (self = [super init]) { + self.m3u8InfoList = [NSMutableArray array]; + } + return self; } - (NSUInteger)count { - return self.m3u8InfoList.count; + return self.m3u8InfoList.count; } -- (void)addExtXMedia:(M3U8ExtXMedia *)extXMedia { - if (extXMedia) { - [self.m3u8InfoList addObject:extXMedia]; - } +- (void)addExtXMedia:(M3U8ExtXMedia*)extXMedia { + if (extXMedia) { + [self.m3u8InfoList addObject:extXMedia]; + } } -- (M3U8ExtXMedia *)xMediaAtIndex:(NSUInteger)index { - if (index >= self.count) { - return nil; - } - return [self.m3u8InfoList objectAtIndex:index]; +- (M3U8ExtXMedia*)xMediaAtIndex:(NSUInteger)index { + if (index >= self.count) { + return nil; + } + return [self.m3u8InfoList objectAtIndex:index]; } -- (M3U8ExtXMedia *)firstExtXMedia { - return self.m3u8InfoList.firstObject; +- (M3U8ExtXMedia*)firstExtXMedia { + return self.m3u8InfoList.firstObject; } -- (M3U8ExtXMedia *)lastExtXMedia { - return self.m3u8InfoList.lastObject; +- (M3U8ExtXMedia*)lastExtXMedia { + return self.m3u8InfoList.lastObject; } -- (M3U8ExtXMediaList *)audioList { - M3U8ExtXMediaList *audioList = [[M3U8ExtXMediaList alloc] init]; - NSArray *copy = [self.m3u8InfoList copy]; - for (M3U8ExtXMedia *media in copy) { - if ([media.type isEqualToString:@"AUDIO"]) { - [audioList addExtXMedia:media]; - } +- (M3U8ExtXMediaList*)audioList { + M3U8ExtXMediaList* audioList = [[M3U8ExtXMediaList alloc] init]; + NSArray* copy = [self.m3u8InfoList copy]; + for (M3U8ExtXMedia* media in copy) { + if ([media.type isEqualToString:@"AUDIO"]) { + [audioList addExtXMedia:media]; } - return audioList; + } + return audioList; } -- (M3U8ExtXMedia *)suitableAudio { - NSString *lan = [NSLocale preferredLanguages].firstObject; - NSArray *copy = [self.m3u8InfoList copy]; - M3U8ExtXMedia *suitableAudio = nil; - for (M3U8ExtXMedia *media in copy) { - if ([media.type isEqualToString:@"AUDIO"]) { - if (nil == suitableAudio) { - suitableAudio = media; - } - if ([media.language isEqualToString:lan]) { - suitableAudio = media; - } - } +- (M3U8ExtXMedia*)suitableAudio { + NSString* lan = [NSLocale preferredLanguages].firstObject; + NSArray* copy = [self.m3u8InfoList copy]; + M3U8ExtXMedia* suitableAudio = nil; + for (M3U8ExtXMedia* media in copy) { + if ([media.type isEqualToString:@"AUDIO"]) { + if (nil == suitableAudio) { + suitableAudio = media; + } + if ([media.language isEqualToString:lan]) { + suitableAudio = media; + } } - return suitableAudio; + } + return suitableAudio; } -- (M3U8ExtXMediaList *)videoList { - M3U8ExtXMediaList *videoList = [[M3U8ExtXMediaList alloc] init]; - NSArray *copy = [self.m3u8InfoList copy]; - for (M3U8ExtXMedia *media in copy) { - if ([media.type isEqualToString:@"VIDEO"]) { - [videoList addExtXMedia:media]; - } +- (M3U8ExtXMediaList*)videoList { + M3U8ExtXMediaList* videoList = [[M3U8ExtXMediaList alloc] init]; + NSArray* copy = [self.m3u8InfoList copy]; + for (M3U8ExtXMedia* media in copy) { + if ([media.type isEqualToString:@"VIDEO"]) { + [videoList addExtXMedia:media]; } - return videoList; + } + return videoList; } -- (M3U8ExtXMedia *)suitableVideo { - NSString *lan = [NSLocale preferredLanguages].firstObject; - NSArray *copy = [self.m3u8InfoList copy]; - M3U8ExtXMedia *suitableVideo = nil; - for (M3U8ExtXMedia *media in copy) { - if ([media.type isEqualToString:@"VIDEO"]) { - - if (nil == suitableVideo) { - suitableVideo = media; - } - if ([media.language isEqualToString:lan]) { - suitableVideo = media; - } - } +- (M3U8ExtXMedia*)suitableVideo { + NSString* lan = [NSLocale preferredLanguages].firstObject; + NSArray* copy = [self.m3u8InfoList copy]; + M3U8ExtXMedia* suitableVideo = nil; + for (M3U8ExtXMedia* media in copy) { + if ([media.type isEqualToString:@"VIDEO"]) { + + if (nil == suitableVideo) { + suitableVideo = media; + } + if ([media.language isEqualToString:lan]) { + suitableVideo = media; + } } - return suitableVideo; + } + return suitableVideo; } -- (M3U8ExtXMediaList *)subtitleList { - M3U8ExtXMediaList *subtitleList = [[M3U8ExtXMediaList alloc] init]; - NSArray *copy = [self.m3u8InfoList copy]; - for (M3U8ExtXMedia *media in copy) { - if ([media.type isEqualToString:@"SUBTITLES"]) { - [subtitleList addExtXMedia:media]; - } +- (M3U8ExtXMediaList*)subtitleList { + M3U8ExtXMediaList* subtitleList = [[M3U8ExtXMediaList alloc] init]; + NSArray* copy = [self.m3u8InfoList copy]; + for (M3U8ExtXMedia* media in copy) { + if ([media.type isEqualToString:@"SUBTITLES"]) { + [subtitleList addExtXMedia:media]; } - return subtitleList; + } + return subtitleList; } -- (M3U8ExtXMedia *)suitableSubtitle { - NSString *lan = [NSLocale preferredLanguages].firstObject; - NSArray *copy = [self.m3u8InfoList copy]; - M3U8ExtXMedia *suitableSubtitle = nil; - for (M3U8ExtXMedia *media in copy) { - if ([media.type isEqualToString:@"SUBTITLES"]) { - if (nil == suitableSubtitle) { - suitableSubtitle = media; - } - if ([media.language isEqualToString:lan]) { - suitableSubtitle = media; - } - } +- (M3U8ExtXMedia*)suitableSubtitle { + NSString* lan = [NSLocale preferredLanguages].firstObject; + NSArray* copy = [self.m3u8InfoList copy]; + M3U8ExtXMedia* suitableSubtitle = nil; + for (M3U8ExtXMedia* media in copy) { + if ([media.type isEqualToString:@"SUBTITLES"]) { + if (nil == suitableSubtitle) { + suitableSubtitle = media; + } + if ([media.language isEqualToString:lan]) { + suitableSubtitle = media; + } } - return suitableSubtitle; + } + return suitableSubtitle; } -- (NSString *)description { - return [NSString stringWithString:self.m3u8InfoList.description]; +- (NSString*)description { + return [NSString stringWithString:self.m3u8InfoList.description]; } @end - - - - - - - - - - - - - diff --git a/ios/Video/M3U8Kit/Source/M3U8ExtXStreamInf.h b/ios/Video/M3U8Kit/Source/M3U8ExtXStreamInf.h index b37f26d2ec..4a779c8af2 100644 --- a/ios/Video/M3U8Kit/Source/M3U8ExtXStreamInf.h +++ b/ios/Video/M3U8Kit/Source/M3U8ExtXStreamInf.h @@ -5,84 +5,84 @@ // Created by Jin Sun on 13-4-15. // Copyright (c) 2013年 iLegendSoft. All rights reserved. // -//The MIT License (MIT) +// The MIT License (MIT) // -//Copyright (c) 2015 Sun Jin <jeansunvf@gmail.com> +// Copyright (c) 2015 Sun Jin <jeansunvf@gmail.com> // -//Permission is hereby granted, free of charge, to any person obtaining a copy -//of this software and associated documentation files (the "Software"), to deal -//in the Software without restriction, including without limitation the rights -//to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -//copies of the Software, and to permit persons to whom the Software is -//furnished to do so, subject to the following conditions: +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: // -//The above copyright notice and this permission notice shall be included in all -//copies or substantial portions of the Software. +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. // -//THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -//IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -//FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -//AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -//LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -//OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -//SOFTWARE. +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. #import <Foundation/Foundation.h> -struct MediaResoulution{ - float width; - float height; +struct MediaResoulution { + float width; + float height; }; typedef struct MediaResoulution MediaResoulution; extern MediaResoulution MediaResolutionMake(float width, float height); extern const MediaResoulution MediaResoulutionZero; -NSString * NSStringFromMediaResolution(MediaResoulution resolution); +NSString* NSStringFromMediaResolution(MediaResoulution resolution); /*! @class M3U8SegmentInfo @abstract This is the class indicates #EXT-X-STREAM-INF:<attribute-list> + <URI> in master playlist file. - + /// EXT-X-STREAM-INF @format #EXT-X-STREAM-INF:<attribute-list> <URI> @example #EXT-X-STREAM-INF:AUDIO="600k",BANDWIDTH=915685,PROGRAM-ID=1,CODECS="avc1.42c01e,mp4a.40.2",RESOLUTION=640x360,SUBTITLES="subs" /talks/769/video/600k.m3u8?sponsor=Ripple - + @note The EXT-X-STREAM-INF tag MUST NOT appear in a Media Playlist. #define M3U8_EXT_X_STREAM_INF @"#EXT-X-STREAM-INF:" // EXT-X-STREAM-INF Attributes #define M3U8_EXT_X_STREAM_INF_BANDWIDTH @"BANDWIDTH" // The value is a decimal-integer of bits per second. -#define M3U8_EXT_X_STREAM_INF_PROGRAM_ID @"PROGRAM-ID" // The value is a decimal-integer that uniquely identifies a particular presentation within the scope of the Playlist file. -#define M3U8_EXT_X_STREAM_INF_CODECS @"CODECS" // The value is a quoted-string containing a comma-separated list of formats. -#define M3U8_EXT_X_STREAM_INF_RESOLUTION @"RESOLUTION" // The value is a decimal-resolution describing the approximate encoded horizontal and vertical resolution of video within the presentation. -#define M3U8_EXT_X_STREAM_INF_AUDIO @"AUDIO" // The value is a quoted-string. -#define M3U8_EXT_X_STREAM_INF_VIDEO @"VIDEO" // The value is a quoted-string. -#define M3U8_EXT_X_STREAM_INF_SUBTITLES @"SUBTITLES" // The value is a quoted-string. -#define M3U8_EXT_X_STREAM_INF_CLOSED_CAPTIONS @"CLOSED-CAPTIONS" // The value can be either a quoted-string or an enumerated-string with the value NONE. -#define M3U8_EXT_X_STREAM_INF_URI @"URI" // The value is a enumerated-string containing a URI that identifies the Playlist file. +#define M3U8_EXT_X_STREAM_INF_PROGRAM_ID @"PROGRAM-ID" // The value is a decimal-integer that uniquely identifies a particular +presentation within the scope of the Playlist file. #define M3U8_EXT_X_STREAM_INF_CODECS @"CODECS" // The value is a quoted-string +containing a comma-separated list of formats. #define M3U8_EXT_X_STREAM_INF_RESOLUTION @"RESOLUTION" // The value is a decimal-resolution +describing the approximate encoded horizontal and vertical resolution of video within the presentation. #define M3U8_EXT_X_STREAM_INF_AUDIO +@"AUDIO" // The value is a quoted-string. #define M3U8_EXT_X_STREAM_INF_VIDEO @"VIDEO" // The value is a quoted-string. #define +M3U8_EXT_X_STREAM_INF_SUBTITLES @"SUBTITLES" // The value is a quoted-string. #define M3U8_EXT_X_STREAM_INF_CLOSED_CAPTIONS +@"CLOSED-CAPTIONS" // The value can be either a quoted-string or an enumerated-string with the value NONE. #define M3U8_EXT_X_STREAM_INF_URI +@"URI" // The value is a enumerated-string containing a URI that identifies the Playlist file. */ @interface M3U8ExtXStreamInf : NSObject -@property (nonatomic, readonly, assign) NSInteger bandwidth; -@property (nonatomic, readonly, assign) NSInteger averageBandwidth; -@property (nonatomic, readonly, assign) NSInteger programId; // removed by draft 12 -@property (nonatomic, readonly, copy) NSArray *codecs; -@property (nonatomic, readonly) MediaResoulution resolution; -@property (nonatomic, readonly, copy) NSString *audio; -@property (nonatomic, readonly, copy) NSString *video; -@property (nonatomic, readonly, copy) NSString *subtitles; -@property (nonatomic, readonly, copy) NSString *closedCaptions; -@property (nonatomic, readonly, copy) NSURL *URI; +@property(nonatomic, readonly, assign) NSInteger bandwidth; +@property(nonatomic, readonly, assign) NSInteger averageBandwidth; +@property(nonatomic, readonly, assign) NSInteger programId; // removed by draft 12 +@property(nonatomic, readonly, copy) NSArray* codecs; +@property(nonatomic, readonly) MediaResoulution resolution; +@property(nonatomic, readonly, copy) NSString* audio; +@property(nonatomic, readonly, copy) NSString* video; +@property(nonatomic, readonly, copy) NSString* subtitles; +@property(nonatomic, readonly, copy) NSString* closedCaptions; +@property(nonatomic, readonly, copy) NSURL* URI; -- (instancetype)initWithDictionary:(NSDictionary *)dictionary; +- (instancetype)initWithDictionary:(NSDictionary*)dictionary; -- (NSURL *)m3u8URL; // the absolute url +- (NSURL*)m3u8URL; // the absolute url -- (NSString *)m3u8PlainString; +- (NSString*)m3u8PlainString; @end diff --git a/ios/Video/M3U8Kit/Source/M3U8ExtXStreamInf.m b/ios/Video/M3U8Kit/Source/M3U8ExtXStreamInf.m index 77d7ad2896..1d976581c7 100644 --- a/ios/Video/M3U8Kit/Source/M3U8ExtXStreamInf.m +++ b/ios/Video/M3U8Kit/Source/M3U8ExtXStreamInf.m @@ -5,165 +5,159 @@ // Created by Jin Sun on 13-4-15. // Copyright (c) 2013年 iLegendSoft. All rights reserved. // -//The MIT License (MIT) +// The MIT License (MIT) // -//Copyright (c) 2015 Sun Jin <jeansunvf@gmail.com> +// Copyright (c) 2015 Sun Jin <jeansunvf@gmail.com> // -//Permission is hereby granted, free of charge, to any person obtaining a copy -//of this software and associated documentation files (the "Software"), to deal -//in the Software without restriction, including without limitation the rights -//to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -//copies of the Software, and to permit persons to whom the Software is -//furnished to do so, subject to the following conditions: +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: // -//The above copyright notice and this permission notice shall be included in all -//copies or substantial portions of the Software. +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. // -//THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -//IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -//FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -//AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -//LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -//OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -//SOFTWARE. +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. #import "M3U8ExtXStreamInf.h" #import "M3U8TagsAndAttributes.h" const MediaResoulution MediaResoulutionZero = {0.f, 0.f}; -NSString * NSStringFromMediaResolution(MediaResoulution resolution) { - return [NSString stringWithFormat:@"%gx%g", resolution.width, resolution.height]; +NSString* NSStringFromMediaResolution(MediaResoulution resolution) { + return [NSString stringWithFormat:@"%gx%g", resolution.width, resolution.height]; } -MediaResoulution MediaResolutionFromString(NSString *string) { - NSArray *comps = [string componentsSeparatedByString:@"x"]; - if (comps.count == 2) { - float width = [comps[0] floatValue]; - float height = [comps[1] floatValue]; - return MediaResolutionMake(width, height); - } else { - return MediaResoulutionZero; - } +MediaResoulution MediaResolutionFromString(NSString* string) { + NSArray* comps = [string componentsSeparatedByString:@"x"]; + if (comps.count == 2) { + float width = [comps[0] floatValue]; + float height = [comps[1] floatValue]; + return MediaResolutionMake(width, height); + } else { + return MediaResoulutionZero; + } } MediaResoulution MediaResolutionMake(float width, float height) { - MediaResoulution resolution = {width, height}; - return resolution; + MediaResoulution resolution = {width, height}; + return resolution; } - -@interface M3U8ExtXStreamInf() -@property (nonatomic, strong) NSDictionary *dictionary; -@property (nonatomic) MediaResoulution resolution; +@interface M3U8ExtXStreamInf () +@property(nonatomic, strong) NSDictionary* dictionary; +@property(nonatomic) MediaResoulution resolution; @end @implementation M3U8ExtXStreamInf -- (instancetype)initWithDictionary:(NSDictionary *)dictionary { - if (self = [super init]) { - self.dictionary = dictionary; - self.resolution = MediaResoulutionZero; - } - return self; +- (instancetype)initWithDictionary:(NSDictionary*)dictionary { + if (self = [super init]) { + self.dictionary = dictionary; + self.resolution = MediaResoulutionZero; + } + return self; } -- (NSURL *)baseURL { - return self.dictionary[M3U8_BASE_URL]; +- (NSURL*)baseURL { + return self.dictionary[M3U8_BASE_URL]; } -- (NSURL *)URL { - return self.dictionary[M3U8_URL]; +- (NSURL*)URL { + return self.dictionary[M3U8_URL]; } -- (NSURL *)m3u8URL { - if (self.URI.scheme) { - return self.URI; - } - - return [NSURL URLWithString:self.URI.absoluteString relativeToURL:[self baseURL]]; +- (NSURL*)m3u8URL { + if (self.URI.scheme) { + return self.URI; + } + + return [NSURL URLWithString:self.URI.absoluteString relativeToURL:[self baseURL]]; } - (NSInteger)bandwidth { - return [self.dictionary[M3U8_EXT_X_STREAM_INF_BANDWIDTH] integerValue]; + return [self.dictionary[M3U8_EXT_X_STREAM_INF_BANDWIDTH] integerValue]; } - (NSInteger)averageBandwidth { - return [self.dictionary[M3U8_EXT_X_STREAM_INF_AVERAGE_BANDWIDTH] integerValue]; + return [self.dictionary[M3U8_EXT_X_STREAM_INF_AVERAGE_BANDWIDTH] integerValue]; } - (NSInteger)programId { - return [self.dictionary[M3U8_EXT_X_STREAM_INF_PROGRAM_ID] integerValue]; + return [self.dictionary[M3U8_EXT_X_STREAM_INF_PROGRAM_ID] integerValue]; } -- (NSArray *)codecs { - NSString *codecsString = self.dictionary[M3U8_EXT_X_STREAM_INF_CODECS]; - return [codecsString componentsSeparatedByString:@","]; +- (NSArray*)codecs { + NSString* codecsString = self.dictionary[M3U8_EXT_X_STREAM_INF_CODECS]; + return [codecsString componentsSeparatedByString:@","]; } - (MediaResoulution)resolution { - NSString *rStr = self.dictionary[M3U8_EXT_X_STREAM_INF_RESOLUTION]; - MediaResoulution resolution = MediaResolutionFromString(rStr); - return resolution; + NSString* rStr = self.dictionary[M3U8_EXT_X_STREAM_INF_RESOLUTION]; + MediaResoulution resolution = MediaResolutionFromString(rStr); + return resolution; } -- (NSString *)audio { - return self.dictionary[M3U8_EXT_X_STREAM_INF_AUDIO]; +- (NSString*)audio { + return self.dictionary[M3U8_EXT_X_STREAM_INF_AUDIO]; } -- (NSString *)video { - return self.dictionary[M3U8_EXT_X_STREAM_INF_VIDEO]; +- (NSString*)video { + return self.dictionary[M3U8_EXT_X_STREAM_INF_VIDEO]; } -- (NSString *)subtitles { - return self.dictionary[M3U8_EXT_X_STREAM_INF_SUBTITLES]; +- (NSString*)subtitles { + return self.dictionary[M3U8_EXT_X_STREAM_INF_SUBTITLES]; } -- (NSString *)closedCaptions { - return self.dictionary[M3U8_EXT_X_STREAM_INF_CLOSED_CAPTIONS]; +- (NSString*)closedCaptions { + return self.dictionary[M3U8_EXT_X_STREAM_INF_CLOSED_CAPTIONS]; } -- (NSURL *)URI { - NSString *uriString = [self.dictionary[M3U8_EXT_X_STREAM_INF_URI] stringByReplacingOccurrencesOfString:@" " withString:@"%20"]; - return [NSURL URLWithString:uriString]; +- (NSURL*)URI { + NSString* uriString = [self.dictionary[M3U8_EXT_X_STREAM_INF_URI] stringByReplacingOccurrencesOfString:@" " withString:@"%20"]; + return [NSURL URLWithString:uriString]; } -- (NSString *)description { - return [NSString stringWithString:self.dictionary.description]; +- (NSString*)description { + return [NSString stringWithString:self.dictionary.description]; } /* #EXT-X-STREAM-INF:AUDIO="600k",BANDWIDTH=1049794,AVERAGE-BANDWIDTH=1000000,PROGRAM-ID=1,CODECS="avc1.42c01e,mp4a.40.2",RESOLUTION=640x360,SUBTITLES="subs" main_media_0.m3u8 */ -- (NSString *)m3u8PlainString { - NSMutableString *str = [NSMutableString string]; - [str appendString:M3U8_EXT_X_STREAM_INF]; - if (self.audio.length > 0) { - [str appendString:[NSString stringWithFormat:@"AUDIO=\"%@\"", self.audio]]; - [str appendString:[NSString stringWithFormat:@",BANDWIDTH=%ld", (long)self.bandwidth]]; - } else { - [str appendString:[NSString stringWithFormat:@"BANDWIDTH=%ld", (long)self.bandwidth]]; - } - - if (self.averageBandwidth > 0) { - [str appendString:[NSString stringWithFormat:@",AVERAGE-BANDWIDTH=%ld", (long)self.averageBandwidth]]; - } - - [str appendString:[NSString stringWithFormat:@",PROGRAM-ID=%ld", (long)self.programId]]; - NSString *codecsString = self.dictionary[M3U8_EXT_X_STREAM_INF_CODECS]; - [str appendString:[NSString stringWithFormat:@",CODECS=\"%@\"", codecsString]]; - NSString *rStr = self.dictionary[M3U8_EXT_X_STREAM_INF_RESOLUTION]; - if (rStr.length > 0) { - [str appendString:[NSString stringWithFormat:@",RESOLUTION=%@", rStr]]; - } - [str appendString:[NSString stringWithFormat:@"\n%@", self.URI.absoluteString]]; - return str; +- (NSString*)m3u8PlainString { + NSMutableString* str = [NSMutableString string]; + [str appendString:M3U8_EXT_X_STREAM_INF]; + if (self.audio.length > 0) { + [str appendString:[NSString stringWithFormat:@"AUDIO=\"%@\"", self.audio]]; + [str appendString:[NSString stringWithFormat:@",BANDWIDTH=%ld", (long)self.bandwidth]]; + } else { + [str appendString:[NSString stringWithFormat:@"BANDWIDTH=%ld", (long)self.bandwidth]]; + } + + if (self.averageBandwidth > 0) { + [str appendString:[NSString stringWithFormat:@",AVERAGE-BANDWIDTH=%ld", (long)self.averageBandwidth]]; + } + + [str appendString:[NSString stringWithFormat:@",PROGRAM-ID=%ld", (long)self.programId]]; + NSString* codecsString = self.dictionary[M3U8_EXT_X_STREAM_INF_CODECS]; + [str appendString:[NSString stringWithFormat:@",CODECS=\"%@\"", codecsString]]; + NSString* rStr = self.dictionary[M3U8_EXT_X_STREAM_INF_RESOLUTION]; + if (rStr.length > 0) { + [str appendString:[NSString stringWithFormat:@",RESOLUTION=%@", rStr]]; + } + [str appendString:[NSString stringWithFormat:@"\n%@", self.URI.absoluteString]]; + return str; } @end - - - - - diff --git a/ios/Video/M3U8Kit/Source/M3U8ExtXStreamInfList.h b/ios/Video/M3U8Kit/Source/M3U8ExtXStreamInfList.h index 485c4d0df6..c4786f4db2 100644 --- a/ios/Video/M3U8Kit/Source/M3U8ExtXStreamInfList.h +++ b/ios/Video/M3U8Kit/Source/M3U8ExtXStreamInfList.h @@ -5,39 +5,39 @@ // Created by Jin Sun on 13-4-15. // Copyright (c) 2013年 iLegendSoft. All rights reserved. // -//The MIT License (MIT) +// The MIT License (MIT) // -//Copyright (c) 2015 Sun Jin <jeansunvf@gmail.com> +// Copyright (c) 2015 Sun Jin <jeansunvf@gmail.com> // -//Permission is hereby granted, free of charge, to any person obtaining a copy -//of this software and associated documentation files (the "Software"), to deal -//in the Software without restriction, including without limitation the rights -//to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -//copies of the Software, and to permit persons to whom the Software is -//furnished to do so, subject to the following conditions: +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: // -//The above copyright notice and this permission notice shall be included in all -//copies or substantial portions of the Software. +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. // -//THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -//IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -//FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -//AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -//LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -//OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -//SOFTWARE. +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. -#import <Foundation/Foundation.h> #import "M3U8ExtXStreamInf.h" +#import <Foundation/Foundation.h> @interface M3U8ExtXStreamInfList : NSObject -@property (nonatomic, assign ,readonly) NSUInteger count; +@property(nonatomic, assign, readonly) NSUInteger count; -- (void)addExtXStreamInf:(M3U8ExtXStreamInf *)extStreamInf; -- (M3U8ExtXStreamInf *)xStreamInfAtIndex:(NSUInteger)index; -- (M3U8ExtXStreamInf *)firstStreamInf; -- (M3U8ExtXStreamInf *)lastXStreamInf; +- (void)addExtXStreamInf:(M3U8ExtXStreamInf*)extStreamInf; +- (M3U8ExtXStreamInf*)xStreamInfAtIndex:(NSUInteger)index; +- (M3U8ExtXStreamInf*)firstStreamInf; +- (M3U8ExtXStreamInf*)lastXStreamInf; - (void)sortByBandwidthInOrder:(NSComparisonResult)order; diff --git a/ios/Video/M3U8Kit/Source/M3U8ExtXStreamInfList.m b/ios/Video/M3U8Kit/Source/M3U8ExtXStreamInfList.m index b5030e1709..fe53dddd36 100644 --- a/ios/Video/M3U8Kit/Source/M3U8ExtXStreamInfList.m +++ b/ios/Video/M3U8Kit/Source/M3U8ExtXStreamInfList.m @@ -5,90 +5,90 @@ // Created by Jin Sun on 13-4-15. // Copyright (c) 2013年 iLegendSoft. All rights reserved. // -//The MIT License (MIT) +// The MIT License (MIT) // -//Copyright (c) 2015 Sun Jin <jeansunvf@gmail.com> +// Copyright (c) 2015 Sun Jin <jeansunvf@gmail.com> // -//Permission is hereby granted, free of charge, to any person obtaining a copy -//of this software and associated documentation files (the "Software"), to deal -//in the Software without restriction, including without limitation the rights -//to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -//copies of the Software, and to permit persons to whom the Software is -//furnished to do so, subject to the following conditions: +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: // -//The above copyright notice and this permission notice shall be included in all -//copies or substantial portions of the Software. +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. // -//THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -//IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -//FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -//AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -//LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -//OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -//SOFTWARE. +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. #import "M3U8ExtXStreamInfList.h" @interface M3U8ExtXStreamInfList () -@property (nonatomic, strong) NSMutableArray *m3u8InfoList; +@property(nonatomic, strong) NSMutableArray* m3u8InfoList; @end @implementation M3U8ExtXStreamInfList - (id)init { - self = [super init]; - if (self) { - self.m3u8InfoList = [NSMutableArray array]; - } - - return self; + self = [super init]; + if (self) { + self.m3u8InfoList = [NSMutableArray array]; + } + + return self; } #pragma mark - Getter && Setter - (NSUInteger)count { - return [self.m3u8InfoList count]; + return [self.m3u8InfoList count]; } #pragma mark - Public -- (void)addExtXStreamInf:(M3U8ExtXStreamInf *)extStreamInf { - [self.m3u8InfoList addObject:extStreamInf]; +- (void)addExtXStreamInf:(M3U8ExtXStreamInf*)extStreamInf { + [self.m3u8InfoList addObject:extStreamInf]; } -- (M3U8ExtXStreamInf *)xStreamInfAtIndex:(NSUInteger)index { - if (index >= self.count) { - return nil; - } - return [self.m3u8InfoList objectAtIndex:index]; +- (M3U8ExtXStreamInf*)xStreamInfAtIndex:(NSUInteger)index { + if (index >= self.count) { + return nil; + } + return [self.m3u8InfoList objectAtIndex:index]; } -- (M3U8ExtXStreamInf *)firstStreamInf { - return [self.m3u8InfoList firstObject]; +- (M3U8ExtXStreamInf*)firstStreamInf { + return [self.m3u8InfoList firstObject]; } -- (M3U8ExtXStreamInf *)lastXStreamInf { - return [self.m3u8InfoList lastObject]; +- (M3U8ExtXStreamInf*)lastXStreamInf { + return [self.m3u8InfoList lastObject]; } - (void)sortByBandwidthInOrder:(NSComparisonResult)order { - - NSArray *array = [self.m3u8InfoList sortedArrayUsingComparator:^NSComparisonResult(id obj1, id obj2) { - NSInteger bandwidth1 = ((M3U8ExtXStreamInf *)obj1).bandwidth; - NSInteger bandwidth2 = ((M3U8ExtXStreamInf *)obj2).bandwidth; - if ( bandwidth1 == bandwidth2 ) { - return NSOrderedSame; - } else if (bandwidth1 < bandwidth2) { - return order; - } else { - return order * (-1); - } - }]; - - self.m3u8InfoList = [array mutableCopy]; + + NSArray* array = [self.m3u8InfoList sortedArrayUsingComparator:^NSComparisonResult(id obj1, id obj2) { + NSInteger bandwidth1 = ((M3U8ExtXStreamInf*)obj1).bandwidth; + NSInteger bandwidth2 = ((M3U8ExtXStreamInf*)obj2).bandwidth; + if (bandwidth1 == bandwidth2) { + return NSOrderedSame; + } else if (bandwidth1 < bandwidth2) { + return order; + } else { + return order * (-1); + } + }]; + + self.m3u8InfoList = [array mutableCopy]; } -- (NSString *)description { - return [NSString stringWithFormat:@"%@", self.m3u8InfoList]; +- (NSString*)description { + return [NSString stringWithFormat:@"%@", self.m3u8InfoList]; } @end diff --git a/ios/Video/M3U8Kit/Source/M3U8LineReader.h b/ios/Video/M3U8Kit/Source/M3U8LineReader.h index 419fe0ed9a..da2c430656 100644 --- a/ios/Video/M3U8Kit/Source/M3U8LineReader.h +++ b/ios/Video/M3U8Kit/Source/M3U8LineReader.h @@ -4,35 +4,35 @@ // // Created by Noam Tamim on 22/03/2018. // -//The MIT License (MIT) +// The MIT License (MIT) // -//Copyright (c) 2015 Sun Jin <jeansunvf@gmail.com> +// Copyright (c) 2015 Sun Jin <jeansunvf@gmail.com> // -//Permission is hereby granted, free of charge, to any person obtaining a copy -//of this software and associated documentation files (the "Software"), to deal -//in the Software without restriction, including without limitation the rights -//to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -//copies of the Software, and to permit persons to whom the Software is -//furnished to do so, subject to the following conditions: +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: // -//The above copyright notice and this permission notice shall be included in all -//copies or substantial portions of the Software. +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. // -//THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -//IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -//FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -//AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -//LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -//OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -//SOFTWARE. +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. #import <Foundation/Foundation.h> @interface M3U8LineReader : NSObject - -@property (nonatomic, readonly, strong) NSArray<NSString*>* lines; -@property (atomic, readonly, assign) NSUInteger index; - + +@property(nonatomic, readonly, strong) NSArray<NSString*>* lines; +@property(atomic, readonly, assign) NSUInteger index; + - (instancetype)initWithText:(NSString*)text; - (NSString*)next; diff --git a/ios/Video/M3U8Kit/Source/M3U8LineReader.m b/ios/Video/M3U8Kit/Source/M3U8LineReader.m index 0647fbd21d..d283a2ae4a 100644 --- a/ios/Video/M3U8Kit/Source/M3U8LineReader.m +++ b/ios/Video/M3U8Kit/Source/M3U8LineReader.m @@ -4,51 +4,48 @@ // // Created by Noam Tamim on 22/03/2018. // -//The MIT License (MIT) +// The MIT License (MIT) // -//Copyright (c) 2015 Sun Jin <jeansunvf@gmail.com> +// Copyright (c) 2015 Sun Jin <jeansunvf@gmail.com> // -//Permission is hereby granted, free of charge, to any person obtaining a copy -//of this software and associated documentation files (the "Software"), to deal -//in the Software without restriction, including without limitation the rights -//to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -//copies of the Software, and to permit persons to whom the Software is -//furnished to do so, subject to the following conditions: +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: // -//The above copyright notice and this permission notice shall be included in all -//copies or substantial portions of the Software. +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. // -//THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -//IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -//FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -//AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -//LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -//OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -//SOFTWARE. +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. #import "M3U8LineReader.h" - @implementation M3U8LineReader -- (instancetype)initWithText:(NSString*)text -{ - self = [super init]; - if (self) { - _lines = [text componentsSeparatedByCharactersInSet:[NSCharacterSet newlineCharacterSet]]; - } - return self; +- (instancetype)initWithText:(NSString*)text { + self = [super init]; + if (self) { + _lines = [text componentsSeparatedByCharactersInSet:[NSCharacterSet newlineCharacterSet]]; + } + return self; } - (NSString*)next { - while (_index < _lines.count) { - NSString* line = [_lines[_index] stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]]; - _index++; - - if (line.length > 0) { - return line; - } + while (_index < _lines.count) { + NSString* line = [_lines[_index] stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]]; + _index++; + + if (line.length > 0) { + return line; } - return nil; + } + return nil; } @end - diff --git a/ios/Video/M3U8Kit/Source/M3U8MasterPlaylist.h b/ios/Video/M3U8Kit/Source/M3U8MasterPlaylist.h index 2445a23ec2..0c3fde2157 100644 --- a/ios/Video/M3U8Kit/Source/M3U8MasterPlaylist.h +++ b/ios/Video/M3U8Kit/Source/M3U8MasterPlaylist.h @@ -5,55 +5,55 @@ // Created by Sun Jin on 3/25/14. // Copyright (c) 2014 Jin Sun. All rights reserved. // -//The MIT License (MIT) +// The MIT License (MIT) // -//Copyright (c) 2015 Sun Jin <jeansunvf@gmail.com> +// Copyright (c) 2015 Sun Jin <jeansunvf@gmail.com> // -//Permission is hereby granted, free of charge, to any person obtaining a copy -//of this software and associated documentation files (the "Software"), to deal -//in the Software without restriction, including without limitation the rights -//to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -//copies of the Software, and to permit persons to whom the Software is -//furnished to do so, subject to the following conditions: +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: // -//The above copyright notice and this permission notice shall be included in all -//copies or substantial portions of the Software. +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. // -//THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -//IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -//FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -//AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -//LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -//OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -//SOFTWARE. +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. -#import <Foundation/Foundation.h> -#import "M3U8ExtXStreamInfList.h" #import "M3U8ExtXKey.h" #import "M3U8ExtXMediaList.h" +#import "M3U8ExtXStreamInfList.h" +#import <Foundation/Foundation.h> @interface M3U8MasterPlaylist : NSObject -@property (nonatomic, strong) NSString *name; +@property(nonatomic, strong) NSString* name; -@property (readonly, nonatomic, strong) NSString *version; +@property(readonly, nonatomic, strong) NSString* version; -@property (readonly, nonatomic, copy) NSString *originalText; -@property (readonly, nonatomic, copy) NSURL *baseURL; -@property (readonly, nonatomic, copy) NSURL *originalURL; +@property(readonly, nonatomic, copy) NSString* originalText; +@property(readonly, nonatomic, copy) NSURL* baseURL; +@property(readonly, nonatomic, copy) NSURL* originalURL; -@property (readonly, nonatomic, strong) M3U8ExtXKey *xSessionKey; +@property(readonly, nonatomic, strong) M3U8ExtXKey* xSessionKey; -@property (readonly, nonatomic, strong) M3U8ExtXStreamInfList *xStreamList; -@property (readonly, nonatomic, strong) M3U8ExtXMediaList *xMediaList; +@property(readonly, nonatomic, strong) M3U8ExtXStreamInfList* xStreamList; +@property(readonly, nonatomic, strong) M3U8ExtXMediaList* xMediaList; -- (NSArray *)allStreamURLs; +- (NSArray*)allStreamURLs; -- (M3U8ExtXStreamInfList *)alternativeXStreamInfList; +- (M3U8ExtXStreamInfList*)alternativeXStreamInfList; -- (instancetype)initWithContent:(NSString *)string baseURL:(NSURL *)baseURL; -- (instancetype)initWithContentOfURL:(NSURL *)URL error:(NSError **)error; +- (instancetype)initWithContent:(NSString*)string baseURL:(NSURL*)baseURL; +- (instancetype)initWithContentOfURL:(NSURL*)URL error:(NSError**)error; -- (NSString *)m3u8PlainString; +- (NSString*)m3u8PlainString; @end diff --git a/ios/Video/M3U8Kit/Source/M3U8MasterPlaylist.m b/ios/Video/M3U8Kit/Source/M3U8MasterPlaylist.m index a5634970fb..8d76490810 100644 --- a/ios/Video/M3U8Kit/Source/M3U8MasterPlaylist.m +++ b/ios/Video/M3U8Kit/Source/M3U8MasterPlaylist.m @@ -5,240 +5,216 @@ // Created by Sun Jin on 3/25/14. // Copyright (c) 2014 Jin Sun. All rights reserved. // -//The MIT License (MIT) +// The MIT License (MIT) // -//Copyright (c) 2015 Sun Jin <jeansunvf@gmail.com> +// Copyright (c) 2015 Sun Jin <jeansunvf@gmail.com> // -//Permission is hereby granted, free of charge, to any person obtaining a copy -//of this software and associated documentation files (the "Software"), to deal -//in the Software without restriction, including without limitation the rights -//to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -//copies of the Software, and to permit persons to whom the Software is -//furnished to do so, subject to the following conditions: +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: // -//The above copyright notice and this permission notice shall be included in all -//copies or substantial portions of the Software. +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. // -//THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -//IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -//FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -//AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -//LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -//OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -//SOFTWARE. +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. #import "M3U8MasterPlaylist.h" -#import "NSString+m3u8.h" +#import "M3U8LineReader.h" #import "M3U8TagsAndAttributes.h" +#import "NSString+m3u8.h" #import "NSURL+m3u8.h" -#import "M3U8LineReader.h" -// #define M3U8_EXT_X_STREAM_INF_CLOSED_CAPTIONS @"CLOSED-CAPTIONS" // The value can be either a quoted-string or an enumerated-string with the value NONE. -// NSArray *quotedValueAttrs = @[@"URI", @"KEYFORMAT", @"KEYFORMATVERSIONS", @"GROUP-ID", @"LANGUAGE", @"ASSOC-LANGUAGE", @"NAME", @"INSTREAM-ID", @"CHARACTERISTICS", @"CODECS", @"AUDIO", @"VIDEO", @"SUBTITLES", @"BYTERANGE"]; +// #define M3U8_EXT_X_STREAM_INF_CLOSED_CAPTIONS @"CLOSED-CAPTIONS" // The value can be either a quoted-string or an enumerated-string +// with the value NONE. +// NSArray *quotedValueAttrs = @[@"URI", @"KEYFORMAT", @"KEYFORMATVERSIONS", @"GROUP-ID", @"LANGUAGE", @"ASSOC-LANGUAGE", @"NAME", +// @"INSTREAM-ID", @"CHARACTERISTICS", @"CODECS", @"AUDIO", @"VIDEO", @"SUBTITLES", @"BYTERANGE"]; @interface M3U8MasterPlaylist () -@property (nonatomic, copy) NSString *originalText; -@property (nonatomic, copy) NSURL *baseURL; -@property (nonatomic, copy) NSURL *originalURL; +@property(nonatomic, copy) NSString* originalText; +@property(nonatomic, copy) NSURL* baseURL; +@property(nonatomic, copy) NSURL* originalURL; -@property (nonatomic, strong) NSString *version; +@property(nonatomic, strong) NSString* version; -@property (nonatomic, strong) M3U8ExtXKey *xSessionKey; +@property(nonatomic, strong) M3U8ExtXKey* xSessionKey; -@property (nonatomic, strong) M3U8ExtXStreamInfList *xStreamList; -@property (nonatomic, strong) M3U8ExtXMediaList *xMediaList; +@property(nonatomic, strong) M3U8ExtXStreamInfList* xStreamList; +@property(nonatomic, strong) M3U8ExtXMediaList* xMediaList; @end @implementation M3U8MasterPlaylist -- (instancetype)initWithContent:(NSString *)string baseURL:(NSURL *)baseURL { - if (!string.m3u_isMasterPlaylist) { - return nil; - } - if (self = [super init]) { - self.originalText = string; - self.baseURL = baseURL; - [self parseMasterPlaylist]; - } - return self; +- (instancetype)initWithContent:(NSString*)string baseURL:(NSURL*)baseURL { + if (!string.m3u_isMasterPlaylist) { + return nil; + } + if (self = [super init]) { + self.originalText = string; + self.baseURL = baseURL; + [self parseMasterPlaylist]; + } + return self; } -- (instancetype)initWithContentOfURL:(NSURL *)URL error:(NSError **)error { - if (!URL) { - return nil; - } - - self.originalURL = URL; - - NSString *string = [NSString stringWithContentsOfURL:URL encoding:NSUTF8StringEncoding error:error]; - return [self initWithContent:string baseURL:URL.m3u_realBaseURL]; -} +- (instancetype)initWithContentOfURL:(NSURL*)URL error:(NSError**)error { + if (!URL) { + return nil; + } -- (void)parseMasterPlaylist { - - self.xStreamList = [[M3U8ExtXStreamInfList alloc] init]; - self.xMediaList = [[M3U8ExtXMediaList alloc] init]; - - M3U8LineReader* reader = [[M3U8LineReader alloc] initWithText:self.originalText]; - - while (true) { - - NSString* line = [reader next]; - if (!line) { - break; - } - - // #EXT-X-VERSION:4 - if ([line hasPrefix:M3U8_EXT_X_VERSION]) { - NSRange r_version = [line rangeOfString:M3U8_EXT_X_VERSION]; - self.version = [line substringFromIndex:r_version.location + r_version.length]; - } - - else if ([line hasPrefix:M3U8_EXT_X_SESSION_KEY]) { - NSRange range = [line rangeOfString:M3U8_EXT_X_SESSION_KEY]; - NSString *attribute_list = [line substringFromIndex:range.location + range.length]; - NSMutableDictionary *attr = attribute_list.m3u_attributesFromAssignmentByComma; - - M3U8ExtXKey *sessionKey = [[M3U8ExtXKey alloc] initWithDictionary:attr]; - self.xSessionKey = sessionKey; - } - - // #EXT-X-STREAM-INF:AUDIO="600k",BANDWIDTH=915685,PROGRAM-ID=1,CODECS="avc1.42c01e,mp4a.40.2",RESOLUTION=640x360,SUBTITLES="subs" - // http://hls.ted.com/talks/769/video/600k.m3u8?sponsor=Ripple - else if ([line hasPrefix:M3U8_EXT_X_STREAM_INF]) { - NSRange range = [line rangeOfString:M3U8_EXT_X_STREAM_INF]; - NSString *attribute_list = [line substringFromIndex:range.location + range.length]; - NSMutableDictionary *attr = attribute_list.m3u_attributesFromAssignmentByComma; - - NSString *nextLine = [reader next]; - attr[@"URI"] = nextLine; - if (self.originalURL) { - attr[M3U8_URL] = self.originalURL; - } - - if (self.baseURL) { - attr[M3U8_BASE_URL] = self.baseURL; - } - - M3U8ExtXStreamInf *xStreamInf = [[M3U8ExtXStreamInf alloc] initWithDictionary:attr]; - [self.xStreamList addExtXStreamInf:xStreamInf]; - } - - - // Ignore the following tag, which is not implemented yet. - // #EXT-X-I-FRAME-STREAM-INF:BANDWIDTH=65531,PROGRAM-ID=1,CODECS="avc1.42c00c",RESOLUTION=320x180,URI="/talks/769/video/64k_iframe.m3u8?sponsor=Ripple" - else if ([line hasPrefix:M3U8_EXT_X_I_FRAME_STREAM_INF]) { - - - } - - // #EXT-X-MEDIA:TYPE=AUDIO,GROUP-ID="600k",LANGUAGE="eng",NAME="Audio",AUTOSELECT=YES,DEFAULT=YES,URI="/talks/769/audio/600k.m3u8?sponsor=Ripple",BANDWIDTH=614400 - else if ([line hasPrefix:M3U8_EXT_X_MEDIA]) { - NSRange range = [line rangeOfString:M3U8_EXT_X_MEDIA]; - NSString *attribute_list = [line substringFromIndex:range.location + range.length]; - NSMutableDictionary *attr = attribute_list.m3u_attributesFromAssignmentByComma; - if (self.baseURL.absoluteString.length > 0) { - attr[M3U8_BASE_URL] = self.baseURL; - } - - if (self.originalURL.absoluteString.length > 0) { - attr[M3U8_URL] = self.originalURL; - } - M3U8ExtXMedia *media = [[M3U8ExtXMedia alloc] initWithDictionary:attr]; - [self.xMediaList addExtXMedia:media]; - } - } -} + self.originalURL = URL; -- (NSArray *)allStreamURLs { - NSMutableArray *array = [NSMutableArray array]; - for (int i = 0; i < self.xStreamList.count; i ++) { - M3U8ExtXStreamInf *xsinf = [self.xStreamList xStreamInfAtIndex:i]; - if (xsinf.m3u8URL.absoluteString.length > 0) { - if (NO == [array containsObject:xsinf.m3u8URL]) { - [array addObject:xsinf.m3u8URL]; - } - } - } - return [array copy]; + NSString* string = [NSString stringWithContentsOfURL:URL encoding:NSUTF8StringEncoding error:error]; + return [self initWithContent:string baseURL:URL.m3u_realBaseURL]; } -- (M3U8ExtXStreamInfList *)alternativeXStreamInfList { - - M3U8ExtXStreamInfList *list = [[M3U8ExtXStreamInfList alloc] init]; - - M3U8ExtXStreamInfList *xsilist = self.xStreamList; - for (int index = 0; index < xsilist.count; index ++) { - M3U8ExtXStreamInf *xsinf = [xsilist xStreamInfAtIndex:index]; - BOOL flag = NO; - for (NSString *str in xsinf.codecs) { - if (NO == flag) { - flag = [str hasPrefix:@"avc1"]; - } - } - if (flag) { - [list addExtXStreamInf:xsinf]; - } - } - - // It is only used when the resolution is selected. - // M3U8ExtXMediaList *xmlist = self.masterPlaylist.xMediaList.videoList; - // for (int i = 0; i < xmlist.count; i ++) { - // M3U8ExtXMedia *media = [xmlist extXMediaAtIndex:i]; - // [allAlternativeURLStrings addObject:media.m3u8URL]; - // } - return list; -} - -- (NSString *)m3u8PlainString { - NSMutableString *str = [NSMutableString string]; - [str appendString:M3U8_EXTM3U]; - [str appendString:@"\n"]; - if (self.version.length > 0) { - [str appendString:[NSString stringWithFormat:@"%@%@", M3U8_EXT_X_VERSION, self.version]]; - [str appendString:@"\n"]; - } - for (NSInteger index = 0; index < self.xStreamList.count; index ++) { - M3U8ExtXStreamInf *xsinf = [self.xStreamList xStreamInfAtIndex:index]; - [str appendString:xsinf.m3u8PlainString]; - [str appendString:@"\n"]; - } - - M3U8ExtXMediaList *audioList = self.xMediaList.audioList; - for (NSInteger i = 0; i < audioList.count; i ++) { - NSLog(@"ext x media %ld", (long)i); - M3U8ExtXMedia *media = [audioList xMediaAtIndex:i]; - [str appendString:media.m3u8PlainString]; - [str appendString:@"\n"]; - } - - return str; -} - -@end - - - - - +- (void)parseMasterPlaylist { + self.xStreamList = [[M3U8ExtXStreamInfList alloc] init]; + self.xMediaList = [[M3U8ExtXMediaList alloc] init]; + M3U8LineReader* reader = [[M3U8LineReader alloc] initWithText:self.originalText]; + while (true) { + NSString* line = [reader next]; + if (!line) { + break; + } + // #EXT-X-VERSION:4 + if ([line hasPrefix:M3U8_EXT_X_VERSION]) { + NSRange r_version = [line rangeOfString:M3U8_EXT_X_VERSION]; + self.version = [line substringFromIndex:r_version.location + r_version.length]; + } + else if ([line hasPrefix:M3U8_EXT_X_SESSION_KEY]) { + NSRange range = [line rangeOfString:M3U8_EXT_X_SESSION_KEY]; + NSString* attribute_list = [line substringFromIndex:range.location + range.length]; + NSMutableDictionary* attr = attribute_list.m3u_attributesFromAssignmentByComma; + M3U8ExtXKey* sessionKey = [[M3U8ExtXKey alloc] initWithDictionary:attr]; + self.xSessionKey = sessionKey; + } + // #EXT-X-STREAM-INF:AUDIO="600k",BANDWIDTH=915685,PROGRAM-ID=1,CODECS="avc1.42c01e,mp4a.40.2",RESOLUTION=640x360,SUBTITLES="subs" + // http://hls.ted.com/talks/769/video/600k.m3u8?sponsor=Ripple + else if ([line hasPrefix:M3U8_EXT_X_STREAM_INF]) { + NSRange range = [line rangeOfString:M3U8_EXT_X_STREAM_INF]; + NSString* attribute_list = [line substringFromIndex:range.location + range.length]; + NSMutableDictionary* attr = attribute_list.m3u_attributesFromAssignmentByComma; + + NSString* nextLine = [reader next]; + attr[@"URI"] = nextLine; + if (self.originalURL) { + attr[M3U8_URL] = self.originalURL; + } + + if (self.baseURL) { + attr[M3U8_BASE_URL] = self.baseURL; + } + + M3U8ExtXStreamInf* xStreamInf = [[M3U8ExtXStreamInf alloc] initWithDictionary:attr]; + [self.xStreamList addExtXStreamInf:xStreamInf]; + } + // Ignore the following tag, which is not implemented yet. + // #EXT-X-I-FRAME-STREAM-INF:BANDWIDTH=65531,PROGRAM-ID=1,CODECS="avc1.42c00c",RESOLUTION=320x180,URI="/talks/769/video/64k_iframe.m3u8?sponsor=Ripple" + else if ([line hasPrefix:M3U8_EXT_X_I_FRAME_STREAM_INF]) { + } + // #EXT-X-MEDIA:TYPE=AUDIO,GROUP-ID="600k",LANGUAGE="eng",NAME="Audio",AUTOSELECT=YES,DEFAULT=YES,URI="/talks/769/audio/600k.m3u8?sponsor=Ripple",BANDWIDTH=614400 + else if ([line hasPrefix:M3U8_EXT_X_MEDIA]) { + NSRange range = [line rangeOfString:M3U8_EXT_X_MEDIA]; + NSString* attribute_list = [line substringFromIndex:range.location + range.length]; + NSMutableDictionary* attr = attribute_list.m3u_attributesFromAssignmentByComma; + if (self.baseURL.absoluteString.length > 0) { + attr[M3U8_BASE_URL] = self.baseURL; + } + + if (self.originalURL.absoluteString.length > 0) { + attr[M3U8_URL] = self.originalURL; + } + M3U8ExtXMedia* media = [[M3U8ExtXMedia alloc] initWithDictionary:attr]; + [self.xMediaList addExtXMedia:media]; + } + } +} +- (NSArray*)allStreamURLs { + NSMutableArray* array = [NSMutableArray array]; + for (int i = 0; i < self.xStreamList.count; i++) { + M3U8ExtXStreamInf* xsinf = [self.xStreamList xStreamInfAtIndex:i]; + if (xsinf.m3u8URL.absoluteString.length > 0) { + if (NO == [array containsObject:xsinf.m3u8URL]) { + [array addObject:xsinf.m3u8URL]; + } + } + } + return [array copy]; +} +- (M3U8ExtXStreamInfList*)alternativeXStreamInfList { + M3U8ExtXStreamInfList* list = [[M3U8ExtXStreamInfList alloc] init]; + M3U8ExtXStreamInfList* xsilist = self.xStreamList; + for (int index = 0; index < xsilist.count; index++) { + M3U8ExtXStreamInf* xsinf = [xsilist xStreamInfAtIndex:index]; + BOOL flag = NO; + for (NSString* str in xsinf.codecs) { + if (NO == flag) { + flag = [str hasPrefix:@"avc1"]; + } + } + if (flag) { + [list addExtXStreamInf:xsinf]; + } + } + + // It is only used when the resolution is selected. + // M3U8ExtXMediaList *xmlist = self.masterPlaylist.xMediaList.videoList; + // for (int i = 0; i < xmlist.count; i ++) { + // M3U8ExtXMedia *media = [xmlist extXMediaAtIndex:i]; + // [allAlternativeURLStrings addObject:media.m3u8URL]; + // } + return list; +} +- (NSString*)m3u8PlainString { + NSMutableString* str = [NSMutableString string]; + [str appendString:M3U8_EXTM3U]; + [str appendString:@"\n"]; + if (self.version.length > 0) { + [str appendString:[NSString stringWithFormat:@"%@%@", M3U8_EXT_X_VERSION, self.version]]; + [str appendString:@"\n"]; + } + for (NSInteger index = 0; index < self.xStreamList.count; index++) { + M3U8ExtXStreamInf* xsinf = [self.xStreamList xStreamInfAtIndex:index]; + [str appendString:xsinf.m3u8PlainString]; + [str appendString:@"\n"]; + } + M3U8ExtXMediaList* audioList = self.xMediaList.audioList; + for (NSInteger i = 0; i < audioList.count; i++) { + NSLog(@"ext x media %ld", (long)i); + M3U8ExtXMedia* media = [audioList xMediaAtIndex:i]; + [str appendString:media.m3u8PlainString]; + [str appendString:@"\n"]; + } + return str; +} +@end diff --git a/ios/Video/M3U8Kit/Source/M3U8MediaPlaylist.h b/ios/Video/M3U8Kit/Source/M3U8MediaPlaylist.h index 3b07ed0a7b..b46693b04e 100644 --- a/ios/Video/M3U8Kit/Source/M3U8MediaPlaylist.h +++ b/ios/Video/M3U8Kit/Source/M3U8MediaPlaylist.h @@ -5,59 +5,59 @@ // Created by Sun Jin on 3/26/14. // Copyright (c) 2014 Jin Sun. All rights reserved. // -//The MIT License (MIT) +// The MIT License (MIT) // -//Copyright (c) 2015 Sun Jin <jeansunvf@gmail.com> +// Copyright (c) 2015 Sun Jin <jeansunvf@gmail.com> // -//Permission is hereby granted, free of charge, to any person obtaining a copy -//of this software and associated documentation files (the "Software"), to deal -//in the Software without restriction, including without limitation the rights -//to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -//copies of the Software, and to permit persons to whom the Software is -//furnished to do so, subject to the following conditions: +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: // -//The above copyright notice and this permission notice shall be included in all -//copies or substantial portions of the Software. +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. // -//THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -//IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -//FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -//AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -//LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -//OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -//SOFTWARE. +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. -#import <Foundation/Foundation.h> #import "M3U8SegmentInfoList.h" +#import <Foundation/Foundation.h> typedef enum { - M3U8MediaPlaylistTypeMedia = 0, // The main media stream playlist. - M3U8MediaPlaylistTypeSubtitle = 1, // EXT-X-SUBTITLES TYPE=SUBTITLES - M3U8MediaPlaylistTypeAudio = 2, // EXT-X-MEDIA TYPE=AUDIO - M3U8MediaPlaylistTypeVideo = 3 // EXT-X-MEDIA TYPE=VIDEO + M3U8MediaPlaylistTypeMedia = 0, // The main media stream playlist. + M3U8MediaPlaylistTypeSubtitle = 1, // EXT-X-SUBTITLES TYPE=SUBTITLES + M3U8MediaPlaylistTypeAudio = 2, // EXT-X-MEDIA TYPE=AUDIO + M3U8MediaPlaylistTypeVideo = 3 // EXT-X-MEDIA TYPE=VIDEO } M3U8MediaPlaylistType; @interface M3U8MediaPlaylist : NSObject -@property (nonatomic, strong) NSString *name; +@property(nonatomic, strong) NSString* name; -@property (readonly, nonatomic, strong) NSString *version; +@property(readonly, nonatomic, strong) NSString* version; -@property (readonly, nonatomic, copy) NSString *originalText; -@property (readonly, nonatomic, copy) NSURL *baseURL; +@property(readonly, nonatomic, copy) NSString* originalText; +@property(readonly, nonatomic, copy) NSURL* baseURL; -@property (readonly, nonatomic, copy) NSURL *originalURL; +@property(readonly, nonatomic, copy) NSURL* originalURL; -@property (readonly, nonatomic, strong) M3U8SegmentInfoList *segmentList; +@property(readonly, nonatomic, strong) M3U8SegmentInfoList* segmentList; /** live or replay */ -@property (assign, readonly, nonatomic) BOOL isLive; +@property(assign, readonly, nonatomic) BOOL isLive; -@property (nonatomic) M3U8MediaPlaylistType type; // -1 by default +@property(nonatomic) M3U8MediaPlaylistType type; // -1 by default -- (instancetype)initWithContent:(NSString *)string type:(M3U8MediaPlaylistType)type baseURL:(NSURL *)baseURL; -- (instancetype)initWithContentOfURL:(NSURL *)URL type:(M3U8MediaPlaylistType)type error:(NSError **)error; +- (instancetype)initWithContent:(NSString*)string type:(M3U8MediaPlaylistType)type baseURL:(NSURL*)baseURL; +- (instancetype)initWithContentOfURL:(NSURL*)URL type:(M3U8MediaPlaylistType)type error:(NSError**)error; -- (NSArray *)allSegmentURLs; +- (NSArray*)allSegmentURLs; @end diff --git a/ios/Video/M3U8Kit/Source/M3U8MediaPlaylist.m b/ios/Video/M3U8Kit/Source/M3U8MediaPlaylist.m index 9fe9e8a74b..8d77fe62c0 100644 --- a/ios/Video/M3U8Kit/Source/M3U8MediaPlaylist.m +++ b/ios/Video/M3U8Kit/Source/M3U8MediaPlaylist.m @@ -5,167 +5,165 @@ // Created by Sun Jin on 3/26/14. // Copyright (c) 2014 Jin Sun. All rights reserved. // -//The MIT License (MIT) +// The MIT License (MIT) // -//Copyright (c) 2015 Sun Jin <jeansunvf@gmail.com> +// Copyright (c) 2015 Sun Jin <jeansunvf@gmail.com> // -//Permission is hereby granted, free of charge, to any person obtaining a copy -//of this software and associated documentation files (the "Software"), to deal -//in the Software without restriction, including without limitation the rights -//to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -//copies of the Software, and to permit persons to whom the Software is -//furnished to do so, subject to the following conditions: +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: // -//The above copyright notice and this permission notice shall be included in all -//copies or substantial portions of the Software. +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. // -//THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -//IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -//FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -//AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -//LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -//OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -//SOFTWARE. +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. #import "M3U8MediaPlaylist.h" -#import "NSString+m3u8.h" -#import "M3U8TagsAndAttributes.h" -#import "NSURL+m3u8.h" -#import "M3U8LineReader.h" -#import "M3U8ExtXKey.h" #import "M3U8ExtXByteRange.h" +#import "M3U8ExtXKey.h" +#import "M3U8LineReader.h" +#import "M3U8TagsAndAttributes.h" #import "NSArray+m3u8.h" +#import "NSString+m3u8.h" +#import "NSURL+m3u8.h" -@interface M3U8MediaPlaylist() +@interface M3U8MediaPlaylist () -@property (nonatomic, copy) NSString *originalText; -@property (nonatomic, copy) NSURL *baseURL; -@property (nonatomic, copy) NSURL *originalURL; +@property(nonatomic, copy) NSString* originalText; +@property(nonatomic, copy) NSURL* baseURL; +@property(nonatomic, copy) NSURL* originalURL; -@property (nonatomic, strong) NSString *version; +@property(nonatomic, strong) NSString* version; -@property (nonatomic, strong) M3U8SegmentInfoList *segmentList; +@property(nonatomic, strong) M3U8SegmentInfoList* segmentList; -@property (assign, nonatomic) BOOL isLive; +@property(assign, nonatomic) BOOL isLive; @end @implementation M3U8MediaPlaylist -- (instancetype)initWithContent:(NSString *)string type:(M3U8MediaPlaylistType)type baseURL:(NSURL *)baseURL { - if (!string.m3u_isMediaPlaylist) { - return nil; - } - - if (self = [super init]) { - self.originalText = string; - self.baseURL = baseURL; - self.type = type; - [self parseMediaPlaylist]; - } - return self; +- (instancetype)initWithContent:(NSString*)string type:(M3U8MediaPlaylistType)type baseURL:(NSURL*)baseURL { + if (!string.m3u_isMediaPlaylist) { + return nil; + } + + if (self = [super init]) { + self.originalText = string; + self.baseURL = baseURL; + self.type = type; + [self parseMediaPlaylist]; + } + return self; } -- (instancetype)initWithContentOfURL:(NSURL *)URL type:(M3U8MediaPlaylistType)type error:(NSError **)error { - if (nil == URL) { - return nil; - } - - self.originalURL = URL; - - NSString *string = [[NSString alloc] initWithContentsOfURL:URL encoding:NSUTF8StringEncoding error:error]; - - return [self initWithContent:string type:type baseURL:URL.m3u_realBaseURL]; +- (instancetype)initWithContentOfURL:(NSURL*)URL type:(M3U8MediaPlaylistType)type error:(NSError**)error { + if (nil == URL) { + return nil; + } + + self.originalURL = URL; + + NSString* string = [[NSString alloc] initWithContentsOfURL:URL encoding:NSUTF8StringEncoding error:error]; + + return [self initWithContent:string type:type baseURL:URL.m3u_realBaseURL]; } -- (NSArray *)allSegmentURLs { - NSMutableArray *array = [NSMutableArray array]; - for (int i = 0; i < self.segmentList.count; i ++) { - M3U8SegmentInfo *info = [self.segmentList segmentInfoAtIndex:i]; - if (info.mediaURL.absoluteString.length > 0) { - if (NO == [array containsObject:info.mediaURL]) { - [array addObject:info.mediaURL]; - } - } +- (NSArray*)allSegmentURLs { + NSMutableArray* array = [NSMutableArray array]; + for (int i = 0; i < self.segmentList.count; i++) { + M3U8SegmentInfo* info = [self.segmentList segmentInfoAtIndex:i]; + if (info.mediaURL.absoluteString.length > 0) { + if (NO == [array containsObject:info.mediaURL]) { + [array addObject:info.mediaURL]; + } } - return [array copy]; + } + return [array copy]; } -- (void)parseMediaPlaylist -{ - self.segmentList = [[M3U8SegmentInfoList alloc] init]; - BOOL isLive = [self.originalText rangeOfString:M3U8_EXT_X_ENDLIST].location == NSNotFound; - self.isLive = isLive; - - M3U8LineReader* reader = [[M3U8LineReader alloc] initWithText:self.originalText]; - M3U8ExtXKey *key = nil; - - while (true) { - - NSString* line = [reader next]; - if (!line) { - break; - } - - NSMutableDictionary *params = [[NSMutableDictionary alloc] init]; - if (self.originalURL) { - [params setObject:self.originalURL forKey:M3U8_URL]; - } - - if (self.baseURL) { - [params setObject:self.baseURL forKey:M3U8_BASE_URL]; - } - - if ([line hasPrefix:M3U8_EXT_X_KEY]) { - line = [line stringByReplacingOccurrencesOfString:M3U8_EXT_X_KEY withString:@""]; - key = [[M3U8ExtXKey alloc] initWithDictionary:line.m3u_attributesFromAssignmentByComma]; - } - - //check if it's #EXTINF: - if ([line hasPrefix:M3U8_EXTINF]) { - line = [line stringByReplacingOccurrencesOfString:M3U8_EXTINF withString:@""]; - - NSArray<NSString *> *components = [line componentsSeparatedByString:@","]; - NSString *info = components.firstObject; - if (info) { - NSString *blankMark = @" "; - NSArray<NSString *> *additions = [info componentsSeparatedByString:blankMark]; - // get duration - NSString *duration = additions.firstObject; - params[M3U8_EXTINF_DURATION] = duration; - - // get additional parameters from Extended M3U https://en.wikipedia.org/wiki/M3U#Extended_M3U - if (additions.count > 1) { - // no need remove duration(first element). `m3u_attributesFromAssignmentByMark` function will skip first non-equation value. - params[M3U8_EXTINF_ADDITIONAL_PARAMETERS] = [additions m3u_attributesFromAssignmentByMark:blankMark]; - } - } - if (components.count > 1) { - params[M3U8_EXTINF_TITLE] = components[1]; - } - - line = reader.next; - // read ByteRange. only for version 4 - M3U8ExtXByteRange *byteRange = nil; - if ([line hasPrefix:M3U8_EXT_X_BYTERANGE]) { - line = [line stringByReplacingOccurrencesOfString:M3U8_EXT_X_BYTERANGE withString:@""]; - byteRange = [[M3U8ExtXByteRange alloc] initWithAtString:line]; - line = reader.next; - } - //ignore other # message - while ([line hasPrefix:@"#"]) { - line = reader.next; - } - //then get URI - params[M3U8_EXTINF_URI] = line; - - M3U8SegmentInfo *segment = [[M3U8SegmentInfo alloc] initWithDictionary:params xKey:key byteRange:byteRange]; - if (segment) { - [self.segmentList addSegementInfo:segment]; - } +- (void)parseMediaPlaylist { + self.segmentList = [[M3U8SegmentInfoList alloc] init]; + BOOL isLive = [self.originalText rangeOfString:M3U8_EXT_X_ENDLIST].location == NSNotFound; + self.isLive = isLive; + + M3U8LineReader* reader = [[M3U8LineReader alloc] initWithText:self.originalText]; + M3U8ExtXKey* key = nil; + + while (true) { + + NSString* line = [reader next]; + if (!line) { + break; + } + + NSMutableDictionary* params = [[NSMutableDictionary alloc] init]; + if (self.originalURL) { + [params setObject:self.originalURL forKey:M3U8_URL]; + } + + if (self.baseURL) { + [params setObject:self.baseURL forKey:M3U8_BASE_URL]; + } + + if ([line hasPrefix:M3U8_EXT_X_KEY]) { + line = [line stringByReplacingOccurrencesOfString:M3U8_EXT_X_KEY withString:@""]; + key = [[M3U8ExtXKey alloc] initWithDictionary:line.m3u_attributesFromAssignmentByComma]; + } + + // check if it's #EXTINF: + if ([line hasPrefix:M3U8_EXTINF]) { + line = [line stringByReplacingOccurrencesOfString:M3U8_EXTINF withString:@""]; + + NSArray<NSString*>* components = [line componentsSeparatedByString:@","]; + NSString* info = components.firstObject; + if (info) { + NSString* blankMark = @" "; + NSArray<NSString*>* additions = [info componentsSeparatedByString:blankMark]; + // get duration + NSString* duration = additions.firstObject; + params[M3U8_EXTINF_DURATION] = duration; + + // get additional parameters from Extended M3U https://en.wikipedia.org/wiki/M3U#Extended_M3U + if (additions.count > 1) { + // no need remove duration(first element). `m3u_attributesFromAssignmentByMark` function will skip first non-equation value. + params[M3U8_EXTINF_ADDITIONAL_PARAMETERS] = [additions m3u_attributesFromAssignmentByMark:blankMark]; } + } + if (components.count > 1) { + params[M3U8_EXTINF_TITLE] = components[1]; + } + + line = reader.next; + // read ByteRange. only for version 4 + M3U8ExtXByteRange* byteRange = nil; + if ([line hasPrefix:M3U8_EXT_X_BYTERANGE]) { + line = [line stringByReplacingOccurrencesOfString:M3U8_EXT_X_BYTERANGE withString:@""]; + byteRange = [[M3U8ExtXByteRange alloc] initWithAtString:line]; + line = reader.next; + } + // ignore other # message + while ([line hasPrefix:@"#"]) { + line = reader.next; + } + // then get URI + params[M3U8_EXTINF_URI] = line; + + M3U8SegmentInfo* segment = [[M3U8SegmentInfo alloc] initWithDictionary:params xKey:key byteRange:byteRange]; + if (segment) { + [self.segmentList addSegementInfo:segment]; + } } + } } @end - diff --git a/ios/Video/M3U8Kit/Source/M3U8Parser.h b/ios/Video/M3U8Kit/Source/M3U8Parser.h index 4b23d09859..85306e0e88 100644 --- a/ios/Video/M3U8Kit/Source/M3U8Parser.h +++ b/ios/Video/M3U8Kit/Source/M3U8Parser.h @@ -5,33 +5,33 @@ // Created by Frank on 20-4-16. // Copyright (c) 2020年 M3U8Kit. All rights reserved. // -//The MIT License (MIT) +// The MIT License (MIT) // -//Copyright (c) 2015 Sun Jin <jeansunvf@gmail.com> +// Copyright (c) 2015 Sun Jin <jeansunvf@gmail.com> // -//Permission is hereby granted, free of charge, to any person obtaining a copy -//of this software and associated documentation files (the "Software"), to deal -//in the Software without restriction, including without limitation the rights -//to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -//copies of the Software, and to permit persons to whom the Software is -//furnished to do so, subject to the following conditions: +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: // -//The above copyright notice and this permission notice shall be included in all -//copies or substantial portions of the Software. +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. // -//THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -//IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -//FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -//AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -//LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -//OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -//SOFTWARE. +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. -#import "M3U8ExtXStreamInf.h" -#import "M3U8ExtXStreamInfList.h" #import "M3U8ExtXKey.h" #import "M3U8ExtXMedia.h" #import "M3U8ExtXMediaList.h" +#import "M3U8ExtXStreamInf.h" +#import "M3U8ExtXStreamInfList.h" #import "M3U8SegmentInfo.h" #import "M3U8SegmentInfoList.h" @@ -39,8 +39,8 @@ #import "M3U8MediaPlaylist.h" #import "M3U8PlaylistModel.h" -#import "NSString+m3u8.h" -#import "NSURL+m3u8.h" -#import "NSArray+m3u8.h" #import "M3U8ExtXByteRange.h" #import "M3U8TagsAndAttributes.h" +#import "NSArray+m3u8.h" +#import "NSString+m3u8.h" +#import "NSURL+m3u8.h" diff --git a/ios/Video/M3U8Kit/Source/M3U8PlaylistModel.h b/ios/Video/M3U8Kit/Source/M3U8PlaylistModel.h index 2e371c1166..abf25fe49b 100644 --- a/ios/Video/M3U8Kit/Source/M3U8PlaylistModel.h +++ b/ios/Video/M3U8Kit/Source/M3U8PlaylistModel.h @@ -5,31 +5,31 @@ // Created by Oneday on 13-1-11. // Copyright (c) 2013年 0day. All rights reserved. // -//The MIT License (MIT) +// The MIT License (MIT) // -//Copyright (c) 2015 Sun Jin <jeansunvf@gmail.com> +// Copyright (c) 2015 Sun Jin <jeansunvf@gmail.com> // -//Permission is hereby granted, free of charge, to any person obtaining a copy -//of this software and associated documentation files (the "Software"), to deal -//in the Software without restriction, including without limitation the rights -//to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -//copies of the Software, and to permit persons to whom the Software is -//furnished to do so, subject to the following conditions: +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: // -//The above copyright notice and this permission notice shall be included in all -//copies or substantial portions of the Software. +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. // -//THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -//IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -//FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -//AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -//LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -//OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -//SOFTWARE. +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. -#import <Foundation/Foundation.h> #import "M3U8MasterPlaylist.h" #import "M3U8MediaPlaylist.h" +#import <Foundation/Foundation.h> // 用来管理 m3u playlist, 根据 URL 或者 string 生成 master playlist, 从master playlist 生成指定的 media playlist // 生成 master playlist @@ -38,74 +38,61 @@ // 把 master playlist 和 media playlist 中的链接替换 合成为本地服务器可用的playlist - // 此版本为简化版,只考虑 #EXT-X-STREAM-INF Tag 中的信息,其他忽略掉 /** - + 需要下载的内容: - + --- master playlist 中的内容,如果有的话 1. master playlist 2. 默认的 media playlist, 由第一个 #EXT-X-STREAM-INF Tag 指定 3. 音频 如果有一个 #EXT-X-STREAM-INF Tag 的codecs只有音频部分,则认为它的URI指向一个音频文件,应该下载下来,其它的Tag 暂时都简单的忽略掉 - + . 下载各 media playlist 对应的分段内容 - + */ @interface M3U8PlaylistModel : NSObject -@property (readonly, nonatomic, copy) NSURL *baseURL; -@property (readonly, nonatomic, copy) NSURL *originalURL; +@property(readonly, nonatomic, copy) NSURL* baseURL; +@property(readonly, nonatomic, copy) NSURL* originalURL; // 如果初始化时的字符串是 media playlist, masterPlaylist为nil -// M3U8PlaylistModel 默认会按照《需要下载的内容》规则选取默认的playlist,如果需要手动指定获取特定的media playlist, 需调用方法 -specifyVideoURL:(这个在选取视频源的时候会用到); +// M3U8PlaylistModel 默认会按照《需要下载的内容》规则选取默认的playlist,如果需要手动指定获取特定的media playlist, 需调用方法 +// -specifyVideoURL:(这个在选取视频源的时候会用到); -@property (readonly, nonatomic, strong) M3U8MasterPlaylist *masterPlaylist; +@property(readonly, nonatomic, strong) M3U8MasterPlaylist* masterPlaylist; -@property (readonly, nonatomic, strong) M3U8MediaPlaylist *mainMediaPl; -- (void)changeMainMediaPlWithPlaylist:(M3U8MediaPlaylist *)playlist; -@property (readonly, nonatomic, strong) M3U8MediaPlaylist *audioPl; +@property(readonly, nonatomic, strong) M3U8MediaPlaylist* mainMediaPl; +- (void)changeMainMediaPlWithPlaylist:(M3U8MediaPlaylist*)playlist; +@property(readonly, nonatomic, strong) M3U8MediaPlaylist* audioPl; //@property (readonly, nonatomic, strong) M3U8MediaPlaylist *subtitlePl; /** this method is synchronous. so may be **block your thread** that call this method. - + @param URL M3U8 URL @param error error pointer @return playlist model */ -- (id)initWithURL:(NSURL *)URL error:(NSError **)error; -- (id)initWithString:(NSString *)string baseURL:(NSURL *)URL error:(NSError **)error; -- (id)initWithString:(NSString *)string originalURL:(NSURL *)originalURL - baseURL:(NSURL *)baseURL error:(NSError * *)error; +- (id)initWithURL:(NSURL*)URL error:(NSError**)error; +- (id)initWithString:(NSString*)string baseURL:(NSURL*)URL error:(NSError**)error; +- (id)initWithString:(NSString*)string originalURL:(NSURL*)originalURL baseURL:(NSURL*)baseURL error:(NSError**)error; // 改变 mainMediaPl // 这个url必须是master playlist 中多码率url(绝对地址)中的一个 // 这个方法先会去获取url中的内容,生成一个mediaPlaylist,如果内容获取失败,mainMediaPl改变失败 -- (void)specifyVideoURL:(NSURL *)URL completion:(void (^)(BOOL success))completion; +- (void)specifyVideoURL:(NSURL*)URL completion:(void (^)(BOOL success))completion; -- (NSString *)indexPlaylistName; +- (NSString*)indexPlaylistName; -- (NSString *)prefixOfSegmentNameInPlaylist:(M3U8MediaPlaylist *)playlist; -- (NSString *)sufixOfSegmentNameInPlaylist:(M3U8MediaPlaylist *)playlist; +- (NSString*)prefixOfSegmentNameInPlaylist:(M3U8MediaPlaylist*)playlist; +- (NSString*)sufixOfSegmentNameInPlaylist:(M3U8MediaPlaylist*)playlist; -- (NSArray *)segmentNamesForPlaylist:(M3U8MediaPlaylist *)playlist; +- (NSArray*)segmentNamesForPlaylist:(M3U8MediaPlaylist*)playlist; // segment name will be formatted as ["%@%d.%@", prefix, index, sufix] eg. main_media_1.ts -- (void)savePlaylistsToPath:(NSString *)path error:(NSError **)error; +- (void)savePlaylistsToPath:(NSString*)path error:(NSError**)error; @end - - - - - - - - - - - - diff --git a/ios/Video/M3U8Kit/Source/M3U8PlaylistModel.m b/ios/Video/M3U8Kit/Source/M3U8PlaylistModel.m index c6c6a0bf2e..6adcad8931 100644 --- a/ios/Video/M3U8Kit/Source/M3U8PlaylistModel.m +++ b/ios/Video/M3U8Kit/Source/M3U8PlaylistModel.m @@ -5,27 +5,27 @@ // Created by Oneday on 13-1-11. // Copyright (c) 2013年 0day. All rights reserved. // -//The MIT License (MIT) +// The MIT License (MIT) // -//Copyright (c) 2015 Sun Jin <jeansunvf@gmail.com> +// Copyright (c) 2015 Sun Jin <jeansunvf@gmail.com> // -//Permission is hereby granted, free of charge, to any person obtaining a copy -//of this software and associated documentation files (the "Software"), to deal -//in the Software without restriction, including without limitation the rights -//to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -//copies of the Software, and to permit persons to whom the Software is -//furnished to do so, subject to the following conditions: +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: // -//The above copyright notice and this permission notice shall be included in all -//copies or substantial portions of the Software. +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. // -//THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -//IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -//FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -//AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -//LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -//OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -//SOFTWARE. +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. #import "M3U8PlaylistModel.h" #import "NSString+m3u8.h" @@ -37,282 +37,278 @@ #define PREFIX_AUDIO_PLAYLIST @"x_media_audio_" #define PREFIX_SUBTITLES_PLAYLIST @"x_media_subtitles_" -@interface M3U8PlaylistModel() +@interface M3U8PlaylistModel () -@property (nonatomic, copy) NSURL *baseURL; -@property (nonatomic, copy) NSURL *originalURL; +@property(nonatomic, copy) NSURL* baseURL; +@property(nonatomic, copy) NSURL* originalURL; -@property (nonatomic, strong) M3U8MasterPlaylist *masterPlaylist; +@property(nonatomic, strong) M3U8MasterPlaylist* masterPlaylist; -@property (nonatomic, strong) M3U8ExtXStreamInf *currentXStreamInf; +@property(nonatomic, strong) M3U8ExtXStreamInf* currentXStreamInf; -@property (nonatomic, strong) M3U8MediaPlaylist *mainMediaPl; -@property (nonatomic, strong) M3U8MediaPlaylist *audioPl; +@property(nonatomic, strong) M3U8MediaPlaylist* mainMediaPl; +@property(nonatomic, strong) M3U8MediaPlaylist* audioPl; //@property (nonatomic, strong) M3U8MediaPlaylist *subtitlePl; @end @implementation M3U8PlaylistModel -- (id)initWithURL:(NSURL *)URL error:(NSError **)error { - - NSString *str = [[NSString alloc] initWithContentsOfURL:URL encoding:NSUTF8StringEncoding error:error]; - if (*error) { - return nil; - } - - self.originalURL = URL; - - return [self initWithString:str baseURL:URL.m3u_realBaseURL error:error]; +- (id)initWithURL:(NSURL*)URL error:(NSError**)error { + + NSString* str = [[NSString alloc] initWithContentsOfURL:URL encoding:NSUTF8StringEncoding error:error]; + if (*error) { + return nil; + } + + self.originalURL = URL; + + return [self initWithString:str baseURL:URL.m3u_realBaseURL error:error]; } -- (id)initWithString:(NSString *)string baseURL:(NSURL *)baseURL error:(NSError **)error { - return [self initWithString:string originalURL:nil baseURL:baseURL error:error]; +- (id)initWithString:(NSString*)string baseURL:(NSURL*)baseURL error:(NSError**)error { + return [self initWithString:string originalURL:nil baseURL:baseURL error:error]; } -- (id)initWithString:(NSString *)string originalURL:(NSURL *)originalURL - baseURL:(NSURL *)baseURL error:(NSError * *)error { +- (id)initWithString:(NSString*)string originalURL:(NSURL*)originalURL baseURL:(NSURL*)baseURL error:(NSError**)error { - if (!string.m3u_isExtendedM3Ufile) { - *error = [NSError errorWithDomain:@"M3U8PlaylistModel" code:-998 userInfo:@{NSLocalizedDescriptionKey:@"The content is not a m3u8 playlist"}]; - return nil; - } - - if (self = [super init]) { - if (string.m3u_isMasterPlaylist) { - self.originalURL = originalURL; - self.baseURL = baseURL; - self.masterPlaylist = [[M3U8MasterPlaylist alloc] initWithContent:string baseURL:baseURL]; - self.masterPlaylist.name = INDEX_PLAYLIST_NAME; - self.currentXStreamInf = self.masterPlaylist.xStreamList.firstStreamInf; - if (self.currentXStreamInf) { - NSError *ero; - NSURL *m3u8URL = self.currentXStreamInf.m3u8URL; - self.mainMediaPl = [[M3U8MediaPlaylist alloc] initWithContentOfURL:m3u8URL type:M3U8MediaPlaylistTypeMedia error:&ero]; - self.mainMediaPl.name = [NSString stringWithFormat:@"%@0.m3u8", PREFIX_MAIN_MEDIA_PLAYLIST]; - if (ero) { - NSLog(@"Get main media playlist failed, error: %@", ero); - } - } - - // get audioPl - M3U8ExtXStreamInfList *list = self.masterPlaylist.xStreamList; - if (list.count > 1) { - for (int i = 0; i < list.count; i++) { - M3U8ExtXStreamInf *xsinf = [list xStreamInfAtIndex:i]; - if (xsinf.codecs.count == 1 && [xsinf.codecs.firstObject hasPrefix:@"mp4a"]) { - NSURL *audioURL = xsinf.m3u8URL; - self.audioPl = [[M3U8MediaPlaylist alloc] initWithContentOfURL:audioURL type:M3U8MediaPlaylistTypeAudio error:NULL]; - self.audioPl.name = [NSString stringWithFormat:@"%@%d.m3u8", PREFIX_MAIN_MEDIA_PLAYLIST, i]; - break; - } - } - } - - } else if (string.m3u_isMediaPlaylist) { - self.mainMediaPl = [[M3U8MediaPlaylist alloc] initWithContent:string type:M3U8MediaPlaylistTypeMedia baseURL:baseURL]; - self.mainMediaPl.name = INDEX_PLAYLIST_NAME; + if (!string.m3u_isExtendedM3Ufile) { + *error = [NSError errorWithDomain:@"M3U8PlaylistModel" + code:-998 + userInfo:@{NSLocalizedDescriptionKey : @"The content is not a m3u8 playlist"}]; + return nil; + } + + if (self = [super init]) { + if (string.m3u_isMasterPlaylist) { + self.originalURL = originalURL; + self.baseURL = baseURL; + self.masterPlaylist = [[M3U8MasterPlaylist alloc] initWithContent:string baseURL:baseURL]; + self.masterPlaylist.name = INDEX_PLAYLIST_NAME; + self.currentXStreamInf = self.masterPlaylist.xStreamList.firstStreamInf; + if (self.currentXStreamInf) { + NSError* ero; + NSURL* m3u8URL = self.currentXStreamInf.m3u8URL; + self.mainMediaPl = [[M3U8MediaPlaylist alloc] initWithContentOfURL:m3u8URL type:M3U8MediaPlaylistTypeMedia error:&ero]; + self.mainMediaPl.name = [NSString stringWithFormat:@"%@0.m3u8", PREFIX_MAIN_MEDIA_PLAYLIST]; + if (ero) { + NSLog(@"Get main media playlist failed, error: %@", ero); } + } + + // get audioPl + M3U8ExtXStreamInfList* list = self.masterPlaylist.xStreamList; + if (list.count > 1) { + for (int i = 0; i < list.count; i++) { + M3U8ExtXStreamInf* xsinf = [list xStreamInfAtIndex:i]; + if (xsinf.codecs.count == 1 && [xsinf.codecs.firstObject hasPrefix:@"mp4a"]) { + NSURL* audioURL = xsinf.m3u8URL; + self.audioPl = [[M3U8MediaPlaylist alloc] initWithContentOfURL:audioURL type:M3U8MediaPlaylistTypeAudio error:NULL]; + self.audioPl.name = [NSString stringWithFormat:@"%@%d.m3u8", PREFIX_MAIN_MEDIA_PLAYLIST, i]; + break; + } + } + } + + } else if (string.m3u_isMediaPlaylist) { + self.mainMediaPl = [[M3U8MediaPlaylist alloc] initWithContent:string type:M3U8MediaPlaylistTypeMedia baseURL:baseURL]; + self.mainMediaPl.name = INDEX_PLAYLIST_NAME; } - return self; + } + return self; } -- (NSSet *)allAlternativeURLStrings { - NSMutableSet *allAlternativeURLStrings = [NSMutableSet set]; - M3U8ExtXStreamInfList *xsilist = self.masterPlaylist.alternativeXStreamInfList; - for (int index = 0; index < xsilist.count; index ++) { - M3U8ExtXStreamInf *xsinf = [xsilist xStreamInfAtIndex:index]; - [allAlternativeURLStrings addObject:xsinf.m3u8URL]; - } - - return allAlternativeURLStrings; +- (NSSet*)allAlternativeURLStrings { + NSMutableSet* allAlternativeURLStrings = [NSMutableSet set]; + M3U8ExtXStreamInfList* xsilist = self.masterPlaylist.alternativeXStreamInfList; + for (int index = 0; index < xsilist.count; index++) { + M3U8ExtXStreamInf* xsinf = [xsilist xStreamInfAtIndex:index]; + [allAlternativeURLStrings addObject:xsinf.m3u8URL]; + } + + return allAlternativeURLStrings; } -- (void)specifyVideoURL:(NSURL *)URL completion:(void (^)(BOOL))completion { - - dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ - - BOOL success = NO; - - if (URL.absoluteString.length > 0 - && nil != self.masterPlaylist - && [self.allAlternativeURLStrings containsObject:URL]) { - - if ([URL.absoluteString isEqualToString:self.mainMediaPl.originalURL.absoluteString]) { - success = YES; - } else { - NSError *error; - M3U8MediaPlaylist *pl = [[M3U8MediaPlaylist alloc] initWithContentOfURL:URL type:M3U8MediaPlaylistTypeMedia error:&error]; - if (pl) { - self.mainMediaPl = pl; - M3U8ExtXStreamInfList *list = self.masterPlaylist.xStreamList; - if (list.count > 1) { - for (int i = 0; i < list.count; i++) { - M3U8ExtXStreamInf *xsinf = [list xStreamInfAtIndex:i]; - if ([xsinf.m3u8URL.absoluteString isEqualToString:pl.originalURL.absoluteString]) { - pl.name = [NSString stringWithFormat:@"%@%d.m3u8", PREFIX_MAIN_MEDIA_PLAYLIST, i]; - break; - } - } - } - success = YES; - } +- (void)specifyVideoURL:(NSURL*)URL completion:(void (^)(BOOL))completion { + + dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ + BOOL success = NO; + + if (URL.absoluteString.length > 0 && nil != self.masterPlaylist && [self.allAlternativeURLStrings containsObject:URL]) { + + if ([URL.absoluteString isEqualToString:self.mainMediaPl.originalURL.absoluteString]) { + success = YES; + } else { + NSError* error; + M3U8MediaPlaylist* pl = [[M3U8MediaPlaylist alloc] initWithContentOfURL:URL type:M3U8MediaPlaylistTypeMedia error:&error]; + if (pl) { + self.mainMediaPl = pl; + M3U8ExtXStreamInfList* list = self.masterPlaylist.xStreamList; + if (list.count > 1) { + for (int i = 0; i < list.count; i++) { + M3U8ExtXStreamInf* xsinf = [list xStreamInfAtIndex:i]; + if ([xsinf.m3u8URL.absoluteString isEqualToString:pl.originalURL.absoluteString]) { + pl.name = [NSString stringWithFormat:@"%@%d.m3u8", PREFIX_MAIN_MEDIA_PLAYLIST, i]; + break; + } } + } + success = YES; } - - dispatch_async(dispatch_get_main_queue(), ^{ - if (completion) { - completion(success); - } - }); + } + } + + dispatch_async(dispatch_get_main_queue(), ^{ + if (completion) { + completion(success); + } }); + }); } -- (void)changeMainMediaPlWithPlaylist:(M3U8MediaPlaylist *)playlist { - if (playlist - && playlist.type == M3U8MediaPlaylistTypeMedia - && [[self allAlternativeURLStrings] containsObject:playlist.baseURL]) { - - self.mainMediaPl = playlist; - M3U8ExtXStreamInfList *list = self.masterPlaylist.xStreamList; - if (list.count > 1) { - for (int i = 0; i < list.count; i++) { - M3U8ExtXStreamInf *xsinf = [list xStreamInfAtIndex:i]; - if ([xsinf.m3u8URL.absoluteString isEqualToString:playlist.originalURL.absoluteString]) { - playlist.name = [NSString stringWithFormat:@"%@%d.m3u8", PREFIX_MAIN_MEDIA_PLAYLIST, i]; - break; - } - } +- (void)changeMainMediaPlWithPlaylist:(M3U8MediaPlaylist*)playlist { + if (playlist && playlist.type == M3U8MediaPlaylistTypeMedia && [[self allAlternativeURLStrings] containsObject:playlist.baseURL]) { + + self.mainMediaPl = playlist; + M3U8ExtXStreamInfList* list = self.masterPlaylist.xStreamList; + if (list.count > 1) { + for (int i = 0; i < list.count; i++) { + M3U8ExtXStreamInf* xsinf = [list xStreamInfAtIndex:i]; + if ([xsinf.m3u8URL.absoluteString isEqualToString:playlist.originalURL.absoluteString]) { + playlist.name = [NSString stringWithFormat:@"%@%d.m3u8", PREFIX_MAIN_MEDIA_PLAYLIST, i]; + break; } + } } + } } -- (NSString *)prefixOfSegmentNameInPlaylist:(M3U8MediaPlaylist *)playlist { - NSString *prefix = nil; - - switch (playlist.type) { - case M3U8MediaPlaylistTypeMedia: - prefix = @"media_"; - break; - case M3U8MediaPlaylistTypeAudio: - prefix = @"audio_"; - break; - case M3U8MediaPlaylistTypeSubtitle: - prefix = @"subtitle_"; - break; - case M3U8MediaPlaylistTypeVideo: - prefix = @"video_"; - break; - - default: - return @""; - break; - } - return prefix; +- (NSString*)prefixOfSegmentNameInPlaylist:(M3U8MediaPlaylist*)playlist { + NSString* prefix = nil; + + switch (playlist.type) { + case M3U8MediaPlaylistTypeMedia: + prefix = @"media_"; + break; + case M3U8MediaPlaylistTypeAudio: + prefix = @"audio_"; + break; + case M3U8MediaPlaylistTypeSubtitle: + prefix = @"subtitle_"; + break; + case M3U8MediaPlaylistTypeVideo: + prefix = @"video_"; + break; + + default: + return @""; + break; + } + return prefix; } -- (NSString *)sufixOfSegmentNameInPlaylist:(M3U8MediaPlaylist *)playlist { - NSString *prefix = nil; - - switch (playlist.type) { - case M3U8MediaPlaylistTypeMedia: - case M3U8MediaPlaylistTypeVideo: - prefix = @"ts"; - break; - case M3U8MediaPlaylistTypeAudio: - prefix = @"aac"; - break; - case M3U8MediaPlaylistTypeSubtitle: - prefix = @"vtt"; - break; - - default: - return @""; - break; - } - return prefix; +- (NSString*)sufixOfSegmentNameInPlaylist:(M3U8MediaPlaylist*)playlist { + NSString* prefix = nil; + + switch (playlist.type) { + case M3U8MediaPlaylistTypeMedia: + case M3U8MediaPlaylistTypeVideo: + prefix = @"ts"; + break; + case M3U8MediaPlaylistTypeAudio: + prefix = @"aac"; + break; + case M3U8MediaPlaylistTypeSubtitle: + prefix = @"vtt"; + break; + + default: + return @""; + break; + } + return prefix; } -- (NSArray *)segmentNamesForPlaylist:(M3U8MediaPlaylist *)playlist { - - NSString *prefix = [self prefixOfSegmentNameInPlaylist:playlist]; - NSString *sufix = [self sufixOfSegmentNameInPlaylist:playlist]; - NSMutableArray *names = [NSMutableArray array]; - - NSArray *URLs = playlist.allSegmentURLs; - NSUInteger count = playlist.segmentList.count; - NSUInteger index = 0; - for (int i = 0; i < count; i ++) { - M3U8SegmentInfo *inf = [playlist.segmentList segmentInfoAtIndex:i]; - index = [URLs indexOfObject:inf.mediaURL]; - NSString *n = [NSString stringWithFormat:@"%@%lu.%@", prefix, (unsigned long)index, sufix]; - [names addObject:n]; - } - return names; +- (NSArray*)segmentNamesForPlaylist:(M3U8MediaPlaylist*)playlist { + + NSString* prefix = [self prefixOfSegmentNameInPlaylist:playlist]; + NSString* sufix = [self sufixOfSegmentNameInPlaylist:playlist]; + NSMutableArray* names = [NSMutableArray array]; + + NSArray* URLs = playlist.allSegmentURLs; + NSUInteger count = playlist.segmentList.count; + NSUInteger index = 0; + for (int i = 0; i < count; i++) { + M3U8SegmentInfo* inf = [playlist.segmentList segmentInfoAtIndex:i]; + index = [URLs indexOfObject:inf.mediaURL]; + NSString* n = [NSString stringWithFormat:@"%@%lu.%@", prefix, (unsigned long)index, sufix]; + [names addObject:n]; + } + return names; } -- (void)savePlaylistsToPath:(NSString *)path error:(NSError **)error { - - if ([[NSFileManager defaultManager] fileExistsAtPath:path]) { - if (NO == [[NSFileManager defaultManager] removeItemAtPath:path error:error]) { - return; - } +- (void)savePlaylistsToPath:(NSString*)path error:(NSError**)error { + + if ([[NSFileManager defaultManager] fileExistsAtPath:path]) { + if (NO == [[NSFileManager defaultManager] removeItemAtPath:path error:error]) { + return; } - if (NO == [[NSFileManager defaultManager] createDirectoryAtPath:path withIntermediateDirectories:YES attributes:nil error:error]) { - return; + } + if (NO == [[NSFileManager defaultManager] createDirectoryAtPath:path withIntermediateDirectories:YES attributes:nil error:error]) { + return; + } + + if (self.masterPlaylist) { + + // master playlist + NSString* masterContext = self.masterPlaylist.m3u8PlainString; + for (int i = 0; i < self.masterPlaylist.xStreamList.count; i++) { + M3U8ExtXStreamInf* xsinf = [self.masterPlaylist.xStreamList xStreamInfAtIndex:i]; + NSString* name = [NSString stringWithFormat:@"%@%d.m3u8", PREFIX_MAIN_MEDIA_PLAYLIST, i]; + masterContext = [masterContext stringByReplacingOccurrencesOfString:xsinf.URI.absoluteString withString:name]; } - - if (self.masterPlaylist) { - - // master playlist - NSString *masterContext = self.masterPlaylist.m3u8PlainString; - for (int i = 0; i < self.masterPlaylist.xStreamList.count; i ++) { - M3U8ExtXStreamInf *xsinf = [self.masterPlaylist.xStreamList xStreamInfAtIndex:i]; - NSString *name = [NSString stringWithFormat:@"%@%d.m3u8", PREFIX_MAIN_MEDIA_PLAYLIST, i]; - masterContext = [masterContext stringByReplacingOccurrencesOfString:xsinf.URI.absoluteString withString:name]; - } - NSString *mPath = [path stringByAppendingPathComponent:self.indexPlaylistName]; - BOOL success = [masterContext writeToFile:mPath atomically:YES encoding:NSUTF8StringEncoding error:error]; - if (NO == success) { - NSLog(@"M3U8Kit Error: failed to save master playlist to file. error: %@", error?*error:@"null"); - return; - } - - // main media playlist - [self saveMediaPlaylist:self.mainMediaPl toPath:path error:error]; - [self saveMediaPlaylist:self.audioPl toPath:path error:error]; - - } else { - [self saveMediaPlaylist:self.mainMediaPl toPath:path error:error]; + NSString* mPath = [path stringByAppendingPathComponent:self.indexPlaylistName]; + BOOL success = [masterContext writeToFile:mPath atomically:YES encoding:NSUTF8StringEncoding error:error]; + if (NO == success) { + NSLog(@"M3U8Kit Error: failed to save master playlist to file. error: %@", error ? *error : @"null"); + return; } + + // main media playlist + [self saveMediaPlaylist:self.mainMediaPl toPath:path error:error]; + [self saveMediaPlaylist:self.audioPl toPath:path error:error]; + + } else { + [self saveMediaPlaylist:self.mainMediaPl toPath:path error:error]; + } } -- (void)saveMediaPlaylist:(M3U8MediaPlaylist *)playlist toPath:(NSString *)path error:(NSError **)error { - if (nil == playlist) { - return; - } - NSString *mainMediaPlContext = playlist.originalText; - if (mainMediaPlContext.length == 0) { - return; - } - - NSArray *names = [self segmentNamesForPlaylist:playlist]; - for (int i = 0; i < playlist.segmentList.count; i ++) { - M3U8SegmentInfo *sinfo = [playlist.segmentList segmentInfoAtIndex:i]; - mainMediaPlContext = [mainMediaPlContext stringByReplacingOccurrencesOfString:sinfo.URI.absoluteString withString:names[i]]; - } - NSString *mainMediaPlPath = [path stringByAppendingPathComponent:playlist.name]; - BOOL success = [mainMediaPlContext writeToFile:mainMediaPlPath atomically:YES encoding:NSUTF8StringEncoding error:error]; - if (NO == success) { - if (NULL != error) { - NSLog(@"M3U8Kit Error: failed to save mian media playlist to file. error: %@", *error); - } - return; +- (void)saveMediaPlaylist:(M3U8MediaPlaylist*)playlist toPath:(NSString*)path error:(NSError**)error { + if (nil == playlist) { + return; + } + NSString* mainMediaPlContext = playlist.originalText; + if (mainMediaPlContext.length == 0) { + return; + } + + NSArray* names = [self segmentNamesForPlaylist:playlist]; + for (int i = 0; i < playlist.segmentList.count; i++) { + M3U8SegmentInfo* sinfo = [playlist.segmentList segmentInfoAtIndex:i]; + mainMediaPlContext = [mainMediaPlContext stringByReplacingOccurrencesOfString:sinfo.URI.absoluteString withString:names[i]]; + } + NSString* mainMediaPlPath = [path stringByAppendingPathComponent:playlist.name]; + BOOL success = [mainMediaPlContext writeToFile:mainMediaPlPath atomically:YES encoding:NSUTF8StringEncoding error:error]; + if (NO == success) { + if (NULL != error) { + NSLog(@"M3U8Kit Error: failed to save mian media playlist to file. error: %@", *error); } + return; + } } -- (NSString *)indexPlaylistName { - return INDEX_PLAYLIST_NAME; +- (NSString*)indexPlaylistName { + return INDEX_PLAYLIST_NAME; } @end diff --git a/ios/Video/M3U8Kit/Source/M3U8SegmentInfo.h b/ios/Video/M3U8Kit/Source/M3U8SegmentInfo.h index 054f696be8..9c7278d1ef 100644 --- a/ios/Video/M3U8Kit/Source/M3U8SegmentInfo.h +++ b/ios/Video/M3U8Kit/Source/M3U8SegmentInfo.h @@ -5,27 +5,27 @@ // Created by Oneday on 13-1-11. // Copyright (c) 2013年 0day. All rights reserved. // -//The MIT License (MIT) +// The MIT License (MIT) // -//Copyright (c) 2015 Sun Jin <jeansunvf@gmail.com> +// Copyright (c) 2015 Sun Jin <jeansunvf@gmail.com> // -//Permission is hereby granted, free of charge, to any person obtaining a copy -//of this software and associated documentation files (the "Software"), to deal -//in the Software without restriction, including without limitation the rights -//to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -//copies of the Software, and to permit persons to whom the Software is -//furnished to do so, subject to the following conditions: +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: // -//The above copyright notice and this permission notice shall be included in all -//copies or substantial portions of the Software. +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. // -//THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -//IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -//FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -//AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -//LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -//OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -//SOFTWARE. +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. #import <Foundation/Foundation.h> @@ -33,12 +33,12 @@ /*! @class M3U8SegmentInfo @abstract This is the class indicates #EXTINF:<duration>,<title> + media in m3u8 file - + @format #EXTINF:<duration>,<title> #define M3U8_EXTINF @"#EXTINF:" - + #define M3U8_EXTINF_DURATION @"DURATION" #define M3U8_EXTINF_TITLE @"TITLE" #define M3U8_EXTINF_URI @"URI" @@ -47,20 +47,22 @@ */ @interface M3U8SegmentInfo : NSObject -@property (readonly, nonatomic) NSTimeInterval duration; -@property (readonly, nonatomic, copy) NSString *title; -@property (readonly, nonatomic, copy) NSURL *URI; -@property (readonly, nonatomic, strong) M3U8ExtXByteRange *byteRange; +@property(readonly, nonatomic) NSTimeInterval duration; +@property(readonly, nonatomic, copy) NSString* title; +@property(readonly, nonatomic, copy) NSURL* URI; +@property(readonly, nonatomic, strong) M3U8ExtXByteRange* byteRange; /** Key for media data decrytion. may be for this segment or next if no key. */ -@property (readonly, nonatomic, strong) M3U8ExtXKey *xKey; -@property (readonly, nonatomic, strong) NSDictionary<NSString *, NSString *> *additionalParameters; +@property(readonly, nonatomic, strong) M3U8ExtXKey* xKey; +@property(readonly, nonatomic, strong) NSDictionary<NSString*, NSString*>* additionalParameters; -- (instancetype)initWithDictionary:(NSDictionary *)dictionary; +- (instancetype)initWithDictionary:(NSDictionary*)dictionary; -- (instancetype)initWithDictionary:(NSDictionary *)dictionary xKey:(M3U8ExtXKey *)key; +- (instancetype)initWithDictionary:(NSDictionary*)dictionary xKey:(M3U8ExtXKey*)key; -- (instancetype)initWithDictionary:(NSDictionary *)dictionary xKey:(M3U8ExtXKey *)key byteRange:(M3U8ExtXByteRange *)byteRange NS_DESIGNATED_INITIALIZER; +- (instancetype)initWithDictionary:(NSDictionary*)dictionary + xKey:(M3U8ExtXKey*)key + byteRange:(M3U8ExtXByteRange*)byteRange NS_DESIGNATED_INITIALIZER; -- (NSURL *)mediaURL; +- (NSURL*)mediaURL; @end diff --git a/ios/Video/M3U8Kit/Source/M3U8SegmentInfo.m b/ios/Video/M3U8Kit/Source/M3U8SegmentInfo.m index 0f2a8fd084..9074045e3d 100644 --- a/ios/Video/M3U8Kit/Source/M3U8SegmentInfo.m +++ b/ios/Video/M3U8Kit/Source/M3U8SegmentInfo.m @@ -5,35 +5,35 @@ // Created by Oneday on 13-1-11. // Copyright (c) 2013年 0day. All rights reserved. // -//The MIT License (MIT) +// The MIT License (MIT) // -//Copyright (c) 2015 Sun Jin <jeansunvf@gmail.com> +// Copyright (c) 2015 Sun Jin <jeansunvf@gmail.com> // -//Permission is hereby granted, free of charge, to any person obtaining a copy -//of this software and associated documentation files (the "Software"), to deal -//in the Software without restriction, including without limitation the rights -//to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -//copies of the Software, and to permit persons to whom the Software is -//furnished to do so, subject to the following conditions: +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: // -//The above copyright notice and this permission notice shall be included in all -//copies or substantial portions of the Software. +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. // -//THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -//IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -//FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -//AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -//LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -//OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -//SOFTWARE. +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. #import "M3U8SegmentInfo.h" -#import "M3U8TagsAndAttributes.h" -#import "M3U8ExtXKey.h" #import "M3U8ExtXByteRange.h" +#import "M3U8ExtXKey.h" +#import "M3U8TagsAndAttributes.h" -@interface M3U8SegmentInfo() -@property (nonatomic, strong) NSDictionary *dictionary; +@interface M3U8SegmentInfo () +@property(nonatomic, strong) NSDictionary* dictionary; @end @@ -42,64 +42,64 @@ @implementation M3U8SegmentInfo @synthesize xKey = _xKey; - (instancetype)init { - return [self initWithDictionary:nil xKey:nil]; + return [self initWithDictionary:nil xKey:nil]; } -- (instancetype)initWithDictionary:(NSDictionary *)dictionary { - return [self initWithDictionary:dictionary xKey:nil]; +- (instancetype)initWithDictionary:(NSDictionary*)dictionary { + return [self initWithDictionary:dictionary xKey:nil]; } -- (instancetype)initWithDictionary:(NSDictionary *)dictionary xKey:(M3U8ExtXKey *)key { - return [self initWithDictionary:dictionary xKey:key byteRange:nil]; +- (instancetype)initWithDictionary:(NSDictionary*)dictionary xKey:(M3U8ExtXKey*)key { + return [self initWithDictionary:dictionary xKey:key byteRange:nil]; } -- (instancetype)initWithDictionary:(NSDictionary *)dictionary xKey:(M3U8ExtXKey *)key byteRange:(M3U8ExtXByteRange *)byteRange{ - if (self = [super init]) { - _dictionary = dictionary; - _xKey = key; - _byteRange = byteRange; - } - return self; +- (instancetype)initWithDictionary:(NSDictionary*)dictionary xKey:(M3U8ExtXKey*)key byteRange:(M3U8ExtXByteRange*)byteRange { + if (self = [super init]) { + _dictionary = dictionary; + _xKey = key; + _byteRange = byteRange; + } + return self; } -- (NSURL *)baseURL { - return self.dictionary[M3U8_BASE_URL]; +- (NSURL*)baseURL { + return self.dictionary[M3U8_BASE_URL]; } -- (NSURL *)URL { - return self.dictionary[M3U8_URL]; +- (NSURL*)URL { + return self.dictionary[M3U8_URL]; } -- (NSURL *)mediaURL { - if (self.URI.scheme) { - return self.URI; - } - - return [NSURL URLWithString:self.URI.absoluteString relativeToURL:[self baseURL]]; +- (NSURL*)mediaURL { + if (self.URI.scheme) { + return self.URI; + } + + return [NSURL URLWithString:self.URI.absoluteString relativeToURL:[self baseURL]]; } - (NSTimeInterval)duration { - return [self.dictionary[M3U8_EXTINF_DURATION] doubleValue]; + return [self.dictionary[M3U8_EXTINF_DURATION] doubleValue]; } -- (NSString *)title { - return self.dictionary[M3U8_EXTINF_TITLE]; +- (NSString*)title { + return self.dictionary[M3U8_EXTINF_TITLE]; } -- (NSURL *)URI { - NSString *originalUrl = self.dictionary[M3U8_EXTINF_URI]; - NSString *urlString = [originalUrl stringByReplacingOccurrencesOfString:@" " withString:@"%20"]; - return [NSURL URLWithString:urlString]; +- (NSURL*)URI { + NSString* originalUrl = self.dictionary[M3U8_EXTINF_URI]; + NSString* urlString = [originalUrl stringByReplacingOccurrencesOfString:@" " withString:@"%20"]; + return [NSURL URLWithString:urlString]; } -- (NSDictionary<NSString *,NSString *> *)additionalParameters { - return self.dictionary[M3U8_EXTINF_ADDITIONAL_PARAMETERS]; +- (NSDictionary<NSString*, NSString*>*)additionalParameters { + return self.dictionary[M3U8_EXTINF_ADDITIONAL_PARAMETERS]; } -- (NSString *)description { - NSMutableDictionary *dict = [self.dictionary mutableCopy]; - [dict addEntriesFromDictionary:[self.xKey valueForKey:@"dictionary"]]; - return dict.description; +- (NSString*)description { + NSMutableDictionary* dict = [self.dictionary mutableCopy]; + [dict addEntriesFromDictionary:[self.xKey valueForKey:@"dictionary"]]; + return dict.description; } @end diff --git a/ios/Video/M3U8Kit/Source/M3U8SegmentInfoList.h b/ios/Video/M3U8Kit/Source/M3U8SegmentInfoList.h index 5f76d3bc25..890c863e7d 100644 --- a/ios/Video/M3U8Kit/Source/M3U8SegmentInfoList.h +++ b/ios/Video/M3U8Kit/Source/M3U8SegmentInfoList.h @@ -5,36 +5,36 @@ // Created by Oneday on 13-1-11. // Copyright (c) 2013年 0day. All rights reserved. // -//The MIT License (MIT) +// The MIT License (MIT) // -//Copyright (c) 2015 Sun Jin <jeansunvf@gmail.com> +// Copyright (c) 2015 Sun Jin <jeansunvf@gmail.com> // -//Permission is hereby granted, free of charge, to any person obtaining a copy -//of this software and associated documentation files (the "Software"), to deal -//in the Software without restriction, including without limitation the rights -//to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -//copies of the Software, and to permit persons to whom the Software is -//furnished to do so, subject to the following conditions: +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: // -//The above copyright notice and this permission notice shall be included in all -//copies or substantial portions of the Software. +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. // -//THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -//IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -//FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -//AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -//LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -//OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -//SOFTWARE. +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. -#import <Foundation/Foundation.h> #import "M3U8SegmentInfo.h" +#import <Foundation/Foundation.h> @interface M3U8SegmentInfoList : NSObject -@property (nonatomic, assign ,readonly) NSUInteger count; +@property(nonatomic, assign, readonly) NSUInteger count; -- (void)addSegementInfo:(M3U8SegmentInfo *)segment; -- (M3U8SegmentInfo *)segmentInfoAtIndex:(NSUInteger)index; +- (void)addSegementInfo:(M3U8SegmentInfo*)segment; +- (M3U8SegmentInfo*)segmentInfoAtIndex:(NSUInteger)index; @end diff --git a/ios/Video/M3U8Kit/Source/M3U8SegmentInfoList.m b/ios/Video/M3U8Kit/Source/M3U8SegmentInfoList.m index 6e05967831..75d693c6a3 100644 --- a/ios/Video/M3U8Kit/Source/M3U8SegmentInfoList.m +++ b/ios/Video/M3U8Kit/Source/M3U8SegmentInfoList.m @@ -5,63 +5,63 @@ // Created by Oneday on 13-1-11. // Copyright (c) 2013年 0day. All rights reserved. // -//The MIT License (MIT) +// The MIT License (MIT) // -//Copyright (c) 2015 Sun Jin <jeansunvf@gmail.com> +// Copyright (c) 2015 Sun Jin <jeansunvf@gmail.com> // -//Permission is hereby granted, free of charge, to any person obtaining a copy -//of this software and associated documentation files (the "Software"), to deal -//in the Software without restriction, including without limitation the rights -//to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -//copies of the Software, and to permit persons to whom the Software is -//furnished to do so, subject to the following conditions: +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: // -//The above copyright notice and this permission notice shall be included in all -//copies or substantial portions of the Software. +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. // -//THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -//IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -//FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -//AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -//LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -//OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -//SOFTWARE. +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. #import "M3U8SegmentInfoList.h" @interface M3U8SegmentInfoList () -@property (nonatomic, strong) NSMutableArray *segmentInfoList; +@property(nonatomic, strong) NSMutableArray* segmentInfoList; @end @implementation M3U8SegmentInfoList - (id)init { - if (self = [super init]) { - self.segmentInfoList = [NSMutableArray array]; - } - return self; + if (self = [super init]) { + self.segmentInfoList = [NSMutableArray array]; + } + return self; } #pragma mark - Getter && Setter - (NSUInteger)count { - return [self.segmentInfoList count]; + return [self.segmentInfoList count]; } #pragma mark - Public -- (void)addSegementInfo:(M3U8SegmentInfo *)segment { - if (segment) { - [self.segmentInfoList addObject:segment]; - } +- (void)addSegementInfo:(M3U8SegmentInfo*)segment { + if (segment) { + [self.segmentInfoList addObject:segment]; + } } -- (M3U8SegmentInfo *)segmentInfoAtIndex:(NSUInteger)index { - return [self.segmentInfoList objectAtIndex:index]; +- (M3U8SegmentInfo*)segmentInfoAtIndex:(NSUInteger)index { + return [self.segmentInfoList objectAtIndex:index]; } -- (NSString *)description { - return [NSString stringWithFormat:@"%@", self.segmentInfoList]; +- (NSString*)description { + return [NSString stringWithFormat:@"%@", self.segmentInfoList]; } @end diff --git a/ios/Video/M3U8Kit/Source/M3U8TagsAndAttributes.h b/ios/Video/M3U8Kit/Source/M3U8TagsAndAttributes.h index fba1dc69db..2dd6733f91 100644 --- a/ios/Video/M3U8Kit/Source/M3U8TagsAndAttributes.h +++ b/ios/Video/M3U8Kit/Source/M3U8TagsAndAttributes.h @@ -5,27 +5,27 @@ // Created by Sun Jin on 3/24/14. // Copyright (c) 2014 Jin Sun. All rights reserved. // -//The MIT License (MIT) +// The MIT License (MIT) // -//Copyright (c) 2015 Sun Jin <jeansunvf@gmail.com> +// Copyright (c) 2015 Sun Jin <jeansunvf@gmail.com> // -//Permission is hereby granted, free of charge, to any person obtaining a copy -//of this software and associated documentation files (the "Software"), to deal -//in the Software without restriction, including without limitation the rights -//to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -//copies of the Software, and to permit persons to whom the Software is -//furnished to do so, subject to the following conditions: +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: // -//The above copyright notice and this permission notice shall be included in all -//copies or substantial portions of the Software. +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. // -//THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -//IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -//FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -//AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -//LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -//OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -//SOFTWARE. +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. // M3U8 Tags & Attributes definded in Draft Pantos Http Live Streaming 12 http://tools.ietf.org/html/draft-pantos-http-live-streaming-12 @@ -33,184 +33,188 @@ #define M3U8_BASE_URL @"baseURL" - ///------------------------------ /// Standard Tags ///------------------------------ -#define M3U8_EXTM3U @"#EXTM3U" +#define M3U8_EXTM3U @"#EXTM3U" /** @format #EXTINF:<duration> <additional parameters> ...,<title> */ -#define M3U8_EXTINF @"#EXTINF:" -#define M3U8_EXTINF_DURATION @"DURATION" -#define M3U8_EXTINF_TITLE @"TITLE" -#define M3U8_EXTINF_URI @"URI" -#define M3U8_EXTINF_ADDITIONAL_PARAMETERS @"ADDITIONAL_PARAMETERS" - +#define M3U8_EXTINF @"#EXTINF:" +#define M3U8_EXTINF_DURATION @"DURATION" +#define M3U8_EXTINF_TITLE @"TITLE" +#define M3U8_EXTINF_URI @"URI" +#define M3U8_EXTINF_ADDITIONAL_PARAMETERS @"ADDITIONAL_PARAMETERS" /// NEW TAGS /** @format #EXT-X-BYTERANGE:<n>[@<o>] - + @note The EXT-X-BYTERANGE tag appeared in version 4 of the protocol. It MUST NOT appear in a Master Playlist. */ -#define M3U8_EXT_X_BYTERANGE @"#EXT-X-BYTERANGE:" - +#define M3U8_EXT_X_BYTERANGE @"#EXT-X-BYTERANGE:" /** @format #EXT-X-TARGETDURATION:<s> - + @note The EXT-X-TARGETDURATION tag MUST NOT appear in a Master Playlist. */ -#define M3U8_EXT_X_TARGETDURATION @"#EXT-X-TARGETDURATION:" - +#define M3U8_EXT_X_TARGETDURATION @"#EXT-X-TARGETDURATION:" /** @format #EXT-X-MEDIA-SEQUENCE:<number> @note The EXT-X-MEDIA-SEQUENCE tag MUST NOT appear in a Master Playlist. */ -#define M3U8_EXT_X_MEDIA_SEQUENCE @"#EXT-X-MEDIA-SEQUENCE:" +#define M3U8_EXT_X_MEDIA_SEQUENCE @"#EXT-X-MEDIA-SEQUENCE:" /// EXT-X-KEY /** @format #EXT-X-KEY:<attribute-list> ps: We may need download the key file at URI. */ -#define M3U8_EXT_X_KEY @"#EXT-X-KEY:" +#define M3U8_EXT_X_KEY @"#EXT-X-KEY:" // EXT-X-KEY Attributes -#define M3U8_EXT_X_KEY_METHOD @"METHOD" // The value is an enumerated-string that specifies the encryption method. This attribute is REQUIRED. - // The methods defined are: NONE, AES-128, and SAMPLE-AES. -#define M3U8_EXT_X_KEY_URI @"URI" // The value is a quoted-string containing a URI [RFC3986] that specifies how to obtain the key. This attribute is REQUIRED unless the METHOD is NONE. -#define M3U8_EXT_X_KEY_IV @"IV" // The value is a hexadecimal-integer that specifies the Initialization Vector to be used with the key. -#define M3U8_EXT_X_KEY_KEYFORMAT @"KEYFORMAT" // The value is a quoted-string that specifies how the key is represented in the resource identified by the URI -#define M3U8_EXT_X_KEY_KEYFORMATVERSIONS @"KEYFORMATVERSIONS" // The value is a quoted-string containing one or more positive integers separated by the "/" character (for example, "1/3"). +#define M3U8_EXT_X_KEY_METHOD \ + @"METHOD" // The value is an enumerated-string that specifies the encryption method. This attribute is REQUIRED. + // The methods defined are: NONE, AES-128, and SAMPLE-AES. +#define M3U8_EXT_X_KEY_URI \ + @"URI" // The value is a quoted-string containing a URI [RFC3986] that specifies how to obtain the key. This attribute is REQUIRED + // unless the METHOD is NONE. +#define M3U8_EXT_X_KEY_IV @"IV" // The value is a hexadecimal-integer that specifies the Initialization Vector to be used with the key. +#define M3U8_EXT_X_KEY_KEYFORMAT \ + @"KEYFORMAT" // The value is a quoted-string that specifies how the key is represented in the resource identified by the URI +#define M3U8_EXT_X_KEY_KEYFORMATVERSIONS \ + @"KEYFORMATVERSIONS" // The value is a quoted-string containing one or more positive integers separated by the "/" character (for example, + // "1/3"). /// M3U8_EXT_X_SESSION_KEY /** Preload EXT-X-KEY infomations - + @format #EXT-X-SESSION-KEY:<attribute list> - + All attributes defined for the EXT-X-KEY tag (Section 4.3.2.4) are also defined for the EXT-X-SESSION-KEY, except that the value of the METHOD attribute MUST NOT be NONE. If an EXT-X-SESSION-KEY is used, the values of the METHOD, KEYFORMAT and KEYFORMATVERSIONS attributes MUST match any EXT-X-KEY with the same URI value. - + EXT-X-SESSION-KEY tags SHOULD be added if multiple Variant Streams or Renditions use the same encryption keys and formats. A EXT-X -SESSION-KEY tag is not associated with any particular Media Playlist. - + A Master Playlist MUST NOT contain more than one EXT-X-SESSION-KEY tag with the same METHOD, URI, IV, KEYFORMAT, and KEYFORMATVERSIONS attribute values. - + The EXT-X-SESSION-KEY tag is optional.. */ -#define M3U8_EXT_X_SESSION_KEY @"#EXT-X-SESSION-KEY:" +#define M3U8_EXT_X_SESSION_KEY @"#EXT-X-SESSION-KEY:" /// M3U8_EXT_X_SESSION_DATA /** Some customized data between Server & Client The EXT-X-SESSION-DATA tag allows arbitrary session data to be carried in a Master Playlist. - + @format #EXT-X-SESSION-DATA:<attribute list> . @example #EXT-X-SESSION-DATA:DATA-ID="com.example.lyrics",URI="lyrics.json" @example #EXT-X-SESSION-DATA:DATA-ID="com.example.title",LANGUAGE="en",VALUE="This is an example" - - + + The following attributes are defined: - + DATA-ID - + The value of DATA-ID is a quoted-string which identifies that data value. The DATA-ID SHOULD conform to a reverse DNS naming convention, such as "com.example.movie.title"; however, there is no central registration authority, so Playlist authors SHOULD take care to choose a value which is unlikely to collide with others. This attribute is REQUIRED. - + VALUE - + VALUE is a quoted-string. It contains the data identified by DATA-ID. If the LANGUAGE is specified, VALUE SHOULD contain a human-readable string written in the specified language. - + URI - + The value is a quoted-string containing a URI. The resource identified by the URI MUST be formatted as JSON [RFC7159]; otherwise, clients may fail to interpret the resource. - + LANGUAGE - + The value is a quoted-string containing a language tag [RFC5646] that identifies the language of the VALUE. This attribute is OPTIONAL. - + Each EXT-X-SESSION-DATA tag MUST contain either a VALUE or URI attribute, but not both. - + A Playlist MAY contain multiple EXT-X-SESSION-DATA tags with the same DATA-ID attribute. A Playlist MUST NOT contain more than one EXT-X -SESSION-DATA tag with the same DATA-ID attribute and the same LANGUAGE attribute. */ -#define M3U8_EXT_X_SESSION_DATA @"EXT-X-SESSION-DATA:" - +#define M3U8_EXT_X_SESSION_DATA @"EXT-X-SESSION-DATA:" /** @format: #EXT-X-PROGRAM-DATE-TIME:<YYYY-MM-DDThh:mm:ssZ> @note The EXT-X-PROGRAM-DATE-TIME tag MUST NOT appear in a Master Playlist. */ -#define M3U8_EXT_X_PROGRAM_DATE_TIME @"#EXT-X-PROGRAM-DATE-TIME:" - +#define M3U8_EXT_X_PROGRAM_DATE_TIME @"#EXT-X-PROGRAM-DATE-TIME:" /** - @format: #EXT-X-ALLOW-CACHE:<YES|NO> + @format: #EXT-X-ALLOW-CACHE:<YES|NO> @note It MUST NOT occur more than once. */ -#define M3U8_EXT_X_ALLOW_CACHE @"#EXT-X-ALLOW-CACHE:" - +#define M3U8_EXT_X_ALLOW_CACHE @"#EXT-X-ALLOW-CACHE:" /** @format: #EXT-X-PLAYLIST-TYPE:<EVENT|VOD> @note The EXT-X-PLAYLIST-TYPE tag MUST NOT appear in a Master Playlist. */ -#define M3U8_EXT_X_PLAYLIST_TYPE @"#EXT-X-PLAYLIST-TYPE:" - +#define M3U8_EXT_X_PLAYLIST_TYPE @"#EXT-X-PLAYLIST-TYPE:" /** @format: #EXT-X-ENDLIST ps: it MUST NOT occur more than once. @note The EXT-X-ENDLIST tag MUST NOT appear in a Master Playlist. */ -#define M3U8_EXT_X_ENDLIST @"#EXT-X-ENDLIST" - - +#define M3U8_EXT_X_ENDLIST @"#EXT-X-ENDLIST" /// EXT-X-MEDIA /** @format #EXT-X-MEDIA:<attribute-list> , attibute-list: ATTR=<value>,... - @example #EXT-X-MEDIA:TYPE=AUDIO,GROUP-ID="600k",LANGUAGE="eng",NAME="Audio",AUTOSELECT=YES,DEFAULT=YES,URI="/talks/769/audio/600k.m3u8?sponsor=Ripple",BANDWIDTH=614400 + @example + #EXT-X-MEDIA:TYPE=AUDIO,GROUP-ID="600k",LANGUAGE="eng",NAME="Audio",AUTOSELECT=YES,DEFAULT=YES,URI="/talks/769/audio/600k.m3u8?sponsor=Ripple",BANDWIDTH=614400 */ -#define M3U8_EXT_X_MEDIA @"#EXT-X-MEDIA:" +#define M3U8_EXT_X_MEDIA @"#EXT-X-MEDIA:" // EXT-X-MEDIA attributes -#define M3U8_EXT_X_MEDIA_TYPE @"TYPE" // The value is enumerated-string; valid strings are AUDIO, VIDEO, SUBTITLES and CLOSED-CAPTIONS. -#define M3U8_EXT_X_MEDIA_URI @"URI" // The value is a quoted-string containing a URI that identifies the Playlist file. -#define M3U8_EXT_X_MEDIA_GROUP_ID @"GROUP-ID" // The value is a quoted-string identifying a mutually-exclusive group of renditions. -#define M3U8_EXT_X_MEDIA_LANGUAGE @"LANGUAGE" // The value is a quoted-string containing an RFC 5646 [RFC5646] language tag that identifies the primary language used in the rendition. -#define M3U8_EXT_X_MEDIA_ASSOC_LANGUAGE @"ASSOC-LANGUAGE" // The value is a quoted-string containing an RFC 5646 [RFC5646](http://tools.ietf.org/html/rfc5646) language tag that identifies a language that is associated with the rendition. -#define M3U8_EXT_X_MEDIA_NAME @"NAME" // The value is a quoted-string containing a human-readable description of the rendition. -#define M3U8_EXT_X_MEDIA_DEFAULT @"DEFAULT" // The value is an enumerated-string; valid strings are YES and NO. -#define M3U8_EXT_X_MEDIA_AUTOSELECT @"AUTOSELECT" // The value is an enumerated-string; valid strings are YES and NO. -#define M3U8_EXT_X_MEDIA_FORCED @"FORCED" // The value is an enumerated-string; valid strings are YES and NO. -#define M3U8_EXT_X_MEDIA_INSTREAM_ID @"INSTREAM-ID" // The value is a quoted-string that specifies a rendition within the segments in the Media Playlist. -#define M3U8_EXT_X_MEDIA_CHARACTERISTICS @"CHARACTERISTICS" // The value is a quoted-string containing one or more Uniform Type Identifiers [UTI] separated by comma (,) characters. -#define M3U8_EXT_X_MEDIA_BANDWIDTH @"BANDWIDTH" -#define M3U8_EXT_X_MEDIA_CHANNELS @"CHANNELS" +#define M3U8_EXT_X_MEDIA_TYPE @"TYPE" // The value is enumerated-string; valid strings are AUDIO, VIDEO, SUBTITLES and CLOSED-CAPTIONS. +#define M3U8_EXT_X_MEDIA_URI @"URI" // The value is a quoted-string containing a URI that identifies the Playlist file. +#define M3U8_EXT_X_MEDIA_GROUP_ID @"GROUP-ID" // The value is a quoted-string identifying a mutually-exclusive group of renditions. +#define M3U8_EXT_X_MEDIA_LANGUAGE \ + @"LANGUAGE" // The value is a quoted-string containing an RFC 5646 [RFC5646] language tag that identifies the primary language used in the + // rendition. +#define M3U8_EXT_X_MEDIA_ASSOC_LANGUAGE \ + @"ASSOC-LANGUAGE" // The value is a quoted-string containing an RFC 5646 [RFC5646](http://tools.ietf.org/html/rfc5646) language tag that + // identifies a language that is associated with the rendition. +#define M3U8_EXT_X_MEDIA_NAME @"NAME" // The value is a quoted-string containing a human-readable description of the rendition. +#define M3U8_EXT_X_MEDIA_DEFAULT @"DEFAULT" // The value is an enumerated-string; valid strings are YES and NO. +#define M3U8_EXT_X_MEDIA_AUTOSELECT @"AUTOSELECT" // The value is an enumerated-string; valid strings are YES and NO. +#define M3U8_EXT_X_MEDIA_FORCED @"FORCED" // The value is an enumerated-string; valid strings are YES and NO. +#define M3U8_EXT_X_MEDIA_INSTREAM_ID \ + @"INSTREAM-ID" // The value is a quoted-string that specifies a rendition within the segments in the Media Playlist. +#define M3U8_EXT_X_MEDIA_CHARACTERISTICS \ + @"CHARACTERISTICS" // The value is a quoted-string containing one or more Uniform Type Identifiers [UTI] separated by comma (,) + // characters. +#define M3U8_EXT_X_MEDIA_BANDWIDTH @"BANDWIDTH" +#define M3U8_EXT_X_MEDIA_CHANNELS @"CHANNELS" /// EXT-X-STREAM-INF /** @@ -218,111 +222,96 @@ <URI> @example #EXT-X-STREAM-INF:AUDIO="600k",BANDWIDTH=915685,PROGRAM-ID=1,CODECS="avc1.42c01e,mp4a.40.2",RESOLUTION=640x360,SUBTITLES="subs" /talks/769/video/600k.m3u8?sponsor=Ripple - + @note The EXT-X-STREAM-INF tag MUST NOT appear in a Media Playlist. */ -#define M3U8_EXT_X_STREAM_INF @"#EXT-X-STREAM-INF:" +#define M3U8_EXT_X_STREAM_INF @"#EXT-X-STREAM-INF:" // EXT-X-STREAM-INF Attributes -#define M3U8_EXT_X_STREAM_INF_BANDWIDTH @"BANDWIDTH" // The value is a decimal-integer of bits per second. -#define M3U8_EXT_X_STREAM_INF_AVERAGE_BANDWIDTH @"AVERAGE-BANDWIDTH" // The value is a decimal-integer of bits per second. It represents the average segment bit rate of the Variant Stream. -#define M3U8_EXT_X_STREAM_INF_PROGRAM_ID @"PROGRAM-ID" // The value is a decimal-integer that uniquely identifies a particular presentation within the scope of the Playlist file. -#define M3U8_EXT_X_STREAM_INF_CODECS @"CODECS" // The value is a quoted-string containing a comma-separated list of formats. -#define M3U8_EXT_X_STREAM_INF_RESOLUTION @"RESOLUTION" // The value is a decimal-resolution describing the approximate encoded horizontal and vertical resolution of video within the presentation. -#define M3U8_EXT_X_STREAM_INF_AUDIO @"AUDIO" // The value is a quoted-string. -#define M3U8_EXT_X_STREAM_INF_VIDEO @"VIDEO" // The value is a quoted-string. -#define M3U8_EXT_X_STREAM_INF_SUBTITLES @"SUBTITLES" // The value is a quoted-string. -#define M3U8_EXT_X_STREAM_INF_CLOSED_CAPTIONS @"CLOSED-CAPTIONS" // The value can be either a quoted-string or an enumerated-string with the value NONE. -#define M3U8_EXT_X_STREAM_INF_URI @"URI" // The value is a enumerated-string containing a URI that identifies the Playlist file. - - +#define M3U8_EXT_X_STREAM_INF_BANDWIDTH @"BANDWIDTH" // The value is a decimal-integer of bits per second. +#define M3U8_EXT_X_STREAM_INF_AVERAGE_BANDWIDTH \ + @"AVERAGE-BANDWIDTH" // The value is a decimal-integer of bits per second. It represents the average segment bit rate of the Variant + // Stream. +#define M3U8_EXT_X_STREAM_INF_PROGRAM_ID \ + @"PROGRAM-ID" // The value is a decimal-integer that uniquely identifies a particular presentation within the scope of the Playlist file. +#define M3U8_EXT_X_STREAM_INF_CODECS @"CODECS" // The value is a quoted-string containing a comma-separated list of formats. +#define M3U8_EXT_X_STREAM_INF_RESOLUTION \ + @"RESOLUTION" // The value is a decimal-resolution describing the approximate encoded horizontal and vertical resolution of video within + // the presentation. +#define M3U8_EXT_X_STREAM_INF_AUDIO @"AUDIO" // The value is a quoted-string. +#define M3U8_EXT_X_STREAM_INF_VIDEO @"VIDEO" // The value is a quoted-string. +#define M3U8_EXT_X_STREAM_INF_SUBTITLES @"SUBTITLES" // The value is a quoted-string. +#define M3U8_EXT_X_STREAM_INF_CLOSED_CAPTIONS \ + @"CLOSED-CAPTIONS" // The value can be either a quoted-string or an enumerated-string with the value NONE. +#define M3U8_EXT_X_STREAM_INF_URI @"URI" // The value is a enumerated-string containing a URI that identifies the Playlist file. /** @format #EXT-X-DISCONTINUITY @note The EXT-X-DISCONTINUITY tag MUST NOT appear in a Master Playlist. */ -#define M3U8_EXT_X_DISCONTINUITY @"#EXT-X-DISCONTINUITY" - +#define M3U8_EXT_X_DISCONTINUITY @"#EXT-X-DISCONTINUITY" /** @format #EXT-X-DISCONTINUITY-SEQUENCE:<number> where number is a decimal-integer. @note The discontinuity sequence number MUST NOT decrease. A Media Playlist MUST NOT contain more than one EXT-X-DISCONTINUITY-SEQUENCE tag. */ -#define M3U8_EXT_X_DISCONTINUITY_SEQUENCE @"#EXT-X-DISCONTINUITY-SEQUENCE:" - +#define M3U8_EXT_X_DISCONTINUITY_SEQUENCE @"#EXT-X-DISCONTINUITY-SEQUENCE:" /** @format #EXT-X-I-FRAMES-ONLY @note The EXT-X-I-FRAMES-ONLY tag MUST NOT appear in a Master Playlist.(v4) */ -#define M3U8_EXT_X_I_FRAMES_ONLY @"#EXT-X-I-FRAMES-ONLY" - - +#define M3U8_EXT_X_I_FRAMES_ONLY @"#EXT-X-I-FRAMES-ONLY" /// EXT-X-MAP /** @format #EXT-X-MAP:<attribute-list> @note The EXT-X-MAP tag MUST NOT appear in a Master Playlist. */ -#define M3U8_EXT_X_MAP @"#EXT-X-MAP:" +#define M3U8_EXT_X_MAP @"#EXT-X-MAP:" // EXT-X-MAP attributes -#define M3U8_EXT_X_MAP_URI @"URI" // The value is a quoted-string containing a URI that identifies a resource that contains segment header information. This attribute is REQUIRED. -#define M3U8_EXT_X_MAP_BYTERANGE @"BYTERANGE" // The value is a quoted-string specifying a byte range into the resource identified by the URI attribute. - - +#define M3U8_EXT_X_MAP_URI \ + @"URI" // The value is a quoted-string containing a URI that identifies a resource that contains segment header information. This + // attribute is REQUIRED. +#define M3U8_EXT_X_MAP_BYTERANGE \ + @"BYTERANGE" // The value is a quoted-string specifying a byte range into the resource identified by the URI attribute. /// EXT-X-I-FRAME-STREAM-INF /** @format #EXT-X-I-FRAME-STREAM-INF:<attribute-list> - @example #EXT-X-I-FRAME-STREAM-INF:BANDWIDTH=65531,PROGRAM-ID=1,CODECS="avc1.42c00c",RESOLUTION=320x180,URI="/talks/769/video/64k_iframe.m3u8?sponsor=Ripple" - - @note All attributes defined for the EXT-X-STREAM-INF tag (Section 3.4.10) are also defined for the EXT-X-I-FRAME-STREAM-INF tag, except for the AUDIO, SUBTITLES and CLOSED-CAPTIONS attributes. - The EXT-X-I-FRAME-STREAM-INF tag MUST NOT appear in a Media Playlist. + @example + #EXT-X-I-FRAME-STREAM-INF:BANDWIDTH=65531,PROGRAM-ID=1,CODECS="avc1.42c00c",RESOLUTION=320x180,URI="/talks/769/video/64k_iframe.m3u8?sponsor=Ripple" + + @note All attributes defined for the EXT-X-STREAM-INF tag (Section 3.4.10) are also defined for the EXT-X-I-FRAME-STREAM-INF tag, + except for the AUDIO, SUBTITLES and CLOSED-CAPTIONS attributes. The EXT-X-I-FRAME-STREAM-INF tag MUST NOT appear in a Media Playlist. */ -#define M3U8_EXT_X_I_FRAME_STREAM_INF @"#EXT-X-I-FRAME-STREAM-INF:" +#define M3U8_EXT_X_I_FRAME_STREAM_INF @"#EXT-X-I-FRAME-STREAM-INF:" // EXT-X-I-FRAME-STREAM-INF Attributes -#define M3U8_EXT_X_I_FRAME_STREAM_INF_URI @"URI" // The value is a quoted-string containing a URI that identifies the I-frame Playlist file. -#define M3U8_EXT_X_I_FRAME_STREAM_INF_BANDWIDTH @"BANDWIDTH" // The value is a decimal-integer of bits per second. -#define M3U8_EXT_X_I_FRAME_STREAM_INF_PROGRAM_ID @"PROGRAM-ID" // The value is a decimal-integer that uniquely identifies a particular presentation within the scope of the Playlist file. -#define M3U8_EXT_X_I_FRAME_STREAM_INF_CODECS @"CODECS" // The value is a quoted-string containing a comma-separated list of formats. -#define M3U8_EXT_X_I_FRAME_STREAM_INF_RESOLUTION @"RESOLUTION" // The value is a decimal-resolution describing the approximate encoded horizontal and vertical resolution of video within the presentation. -#define M3U8_EXT_X_I_FRAME_STREAM_INF_VIDEO @"VIDEO" // The value is a quoted-string. - - - +#define M3U8_EXT_X_I_FRAME_STREAM_INF_URI @"URI" // The value is a quoted-string containing a URI that identifies the I-frame Playlist file. +#define M3U8_EXT_X_I_FRAME_STREAM_INF_BANDWIDTH @"BANDWIDTH" // The value is a decimal-integer of bits per second. +#define M3U8_EXT_X_I_FRAME_STREAM_INF_PROGRAM_ID \ + @"PROGRAM-ID" // The value is a decimal-integer that uniquely identifies a particular presentation within the scope of the Playlist file. +#define M3U8_EXT_X_I_FRAME_STREAM_INF_CODECS @"CODECS" // The value is a quoted-string containing a comma-separated list of formats. +#define M3U8_EXT_X_I_FRAME_STREAM_INF_RESOLUTION \ + @"RESOLUTION" // The value is a decimal-resolution describing the approximate encoded horizontal and vertical resolution of video within + // the presentation. +#define M3U8_EXT_X_I_FRAME_STREAM_INF_VIDEO @"VIDEO" // The value is a quoted-string. /// EXT-X-START /** @format #EXT-X-START:<attribute list> */ -#define M3U8_EXT_X_START @"#EXT-X-START:" +#define M3U8_EXT_X_START @"#EXT-X-START:" // EXT-X-START Attributes -#define M3U8_EXT_X_START_TIME_OFFSET @"TIME-OFFSET" // The value of TIME-OFFSET is a decimal-floating-point number of seconds. -#define M3U8_EXT_X_START_PRECISE @"PRECISE" // The value is an enumerated-string; valid strings are YES and NO. - - - - +#define M3U8_EXT_X_START_TIME_OFFSET @"TIME-OFFSET" // The value of TIME-OFFSET is a decimal-floating-point number of seconds. +#define M3U8_EXT_X_START_PRECISE @"PRECISE" // The value is an enumerated-string; valid strings are YES and NO. /** @format #EXT-X-VERSION:<n> where n is an integer indicating the protocol version. */ -#define M3U8_EXT_X_VERSION @"#EXT-X-VERSION:" - - - - +#define M3U8_EXT_X_VERSION @"#EXT-X-VERSION:" /** @format #EXT-X-SESSION-KEY:<n> where n is an integer indicating the protocol version. */ -#define M3U8_EXT_X_SESSION_KEY @"#EXT-X-SESSION-KEY:" - - - - - - - - - +#define M3U8_EXT_X_SESSION_KEY @"#EXT-X-SESSION-KEY:" diff --git a/ios/Video/M3U8Kit/Source/NSArray+m3u8.h b/ios/Video/M3U8Kit/Source/NSArray+m3u8.h index 214abff480..3808d9a849 100644 --- a/ios/Video/M3U8Kit/Source/NSArray+m3u8.h +++ b/ios/Video/M3U8Kit/Source/NSArray+m3u8.h @@ -5,27 +5,27 @@ // Created by Frank on 2022/7/12. // Copyright © 2022 M3U8Kit. All rights reserved. // -//The MIT License (MIT) +// The MIT License (MIT) // -//Copyright (c) 2015 Sun Jin <jeansunvf@gmail.com> +// Copyright (c) 2015 Sun Jin <jeansunvf@gmail.com> // -//Permission is hereby granted, free of charge, to any person obtaining a copy -//of this software and associated documentation files (the "Software"), to deal -//in the Software without restriction, including without limitation the rights -//to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -//copies of the Software, and to permit persons to whom the Software is -//furnished to do so, subject to the following conditions: +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: // -//The above copyright notice and this permission notice shall be included in all -//copies or substantial portions of the Software. +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. // -//THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -//IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -//FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -//AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -//LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -//OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -//SOFTWARE. +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. #import <Foundation/Foundation.h> @@ -34,15 +34,15 @@ NS_ASSUME_NONNULL_BEGIN @interface NSArray (m3u8) /** @return "key=value" transform to dictionary */ -- (NSMutableDictionary *)m3u_attributesFromAssignment; +- (NSMutableDictionary*)m3u_attributesFromAssignment; /** If check by invalid value, value will append to last element with specific mark. - + @param mark attribute will be ignored if it is invalid. @return "key=value" transform to dictionary */ -- (NSMutableDictionary *)m3u_attributesFromAssignmentByMark:(nullable NSString *)mark; +- (NSMutableDictionary*)m3u_attributesFromAssignmentByMark:(nullable NSString*)mark; @end diff --git a/ios/Video/M3U8Kit/Source/NSArray+m3u8.m b/ios/Video/M3U8Kit/Source/NSArray+m3u8.m index 724c939427..da9994d24b 100644 --- a/ios/Video/M3U8Kit/Source/NSArray+m3u8.m +++ b/ios/Video/M3U8Kit/Source/NSArray+m3u8.m @@ -5,60 +5,62 @@ // Created by Frank on 2022/7/12. // Copyright © 2022 M3U8Kit. All rights reserved. // -//The MIT License (MIT) +// The MIT License (MIT) // -//Copyright (c) 2015 Sun Jin <jeansunvf@gmail.com> +// Copyright (c) 2015 Sun Jin <jeansunvf@gmail.com> // -//Permission is hereby granted, free of charge, to any person obtaining a copy -//of this software and associated documentation files (the "Software"), to deal -//in the Software without restriction, including without limitation the rights -//to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -//copies of the Software, and to permit persons to whom the Software is -//furnished to do so, subject to the following conditions: +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: // -//The above copyright notice and this permission notice shall be included in all -//copies or substantial portions of the Software. +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. // -//THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -//IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -//FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -//AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -//LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -//OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -//SOFTWARE. +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. #import "NSArray+m3u8.h" #import "NSString+m3u8.h" @implementation NSArray (m3u8) -- (NSMutableDictionary *)m3u_attributesFromAssignment { - return [self m3u_attributesFromAssignmentByMark:nil]; +- (NSMutableDictionary*)m3u_attributesFromAssignment { + return [self m3u_attributesFromAssignmentByMark:nil]; } -- (NSMutableDictionary *)m3u_attributesFromAssignmentByMark:(NSString *)mark { - NSMutableDictionary *dict = [NSMutableDictionary dictionary]; - - NSString *lastkey = nil; - for (NSString *keyValue in self) { - NSRange equalMarkRange = [keyValue rangeOfString:@"="]; - // if equal mark is not found, it means this value is previous value left. eg: CODECS=\"avc1.42c01e,mp4a.40.2\" - if (equalMarkRange.location == NSNotFound) { - if (!mark) continue; - if (!lastkey) continue; - NSString *lastValue = dict[lastkey]; - NSString *supplement = [lastValue stringByAppendingFormat:@"%@%@", mark, keyValue.m3u_stringByTrimmingQuoteMark]; - dict[lastkey] = supplement; - continue; - } - NSString *key = [keyValue substringToIndex:equalMarkRange.location].m3u_stringByTrimmingQuoteMark; - NSString *value = [keyValue substringFromIndex:equalMarkRange.location + 1].m3u_stringByTrimmingQuoteMark; - - dict[key] = value; - lastkey = key; +- (NSMutableDictionary*)m3u_attributesFromAssignmentByMark:(NSString*)mark { + NSMutableDictionary* dict = [NSMutableDictionary dictionary]; + + NSString* lastkey = nil; + for (NSString* keyValue in self) { + NSRange equalMarkRange = [keyValue rangeOfString:@"="]; + // if equal mark is not found, it means this value is previous value left. eg: CODECS=\"avc1.42c01e,mp4a.40.2\" + if (equalMarkRange.location == NSNotFound) { + if (!mark) + continue; + if (!lastkey) + continue; + NSString* lastValue = dict[lastkey]; + NSString* supplement = [lastValue stringByAppendingFormat:@"%@%@", mark, keyValue.m3u_stringByTrimmingQuoteMark]; + dict[lastkey] = supplement; + continue; } - - return dict; + NSString* key = [keyValue substringToIndex:equalMarkRange.location].m3u_stringByTrimmingQuoteMark; + NSString* value = [keyValue substringFromIndex:equalMarkRange.location + 1].m3u_stringByTrimmingQuoteMark; + + dict[key] = value; + lastkey = key; + } + + return dict; } @end diff --git a/ios/Video/M3U8Kit/Source/NSString+m3u8.h b/ios/Video/M3U8Kit/Source/NSString+m3u8.h index e0cf760073..bf2ca100bb 100644 --- a/ios/Video/M3U8Kit/Source/NSString+m3u8.h +++ b/ios/Video/M3U8Kit/Source/NSString+m3u8.h @@ -5,27 +5,27 @@ // Created by Oneday on 13-1-11. // Copyright (c) 2013年 0day. All rights reserved. // -//The MIT License (MIT) +// The MIT License (MIT) // -//Copyright (c) 2015 Sun Jin <jeansunvf@gmail.com> +// Copyright (c) 2015 Sun Jin <jeansunvf@gmail.com> // -//Permission is hereby granted, free of charge, to any person obtaining a copy -//of this software and associated documentation files (the "Software"), to deal -//in the Software without restriction, including without limitation the rights -//to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -//copies of the Software, and to permit persons to whom the Software is -//furnished to do so, subject to the following conditions: +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: // -//The above copyright notice and this permission notice shall be included in all -//copies or substantial portions of the Software. +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. // -//THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -//IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -//FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -//AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -//LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -//OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -//SOFTWARE. +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. #import <Foundation/Foundation.h> @@ -37,15 +37,15 @@ - (BOOL)m3u_isMasterPlaylist; - (BOOL)m3u_isMediaPlaylist; -- (M3U8SegmentInfoList *)m3u_segementInfoListValueRelativeToURL:(NSString *)baseURL; +- (M3U8SegmentInfoList*)m3u_segementInfoListValueRelativeToURL:(NSString*)baseURL; /** @return "key=value" transform to dictionary */ -- (NSMutableDictionary *)m3u_attributesFromAssignmentByMark:(NSString *)mark; -- (NSMutableDictionary *)m3u_attributesFromAssignmentByComma; -- (NSMutableDictionary *)m3u_attributesFromAssignmentByBlank; +- (NSMutableDictionary*)m3u_attributesFromAssignmentByMark:(NSString*)mark; +- (NSMutableDictionary*)m3u_attributesFromAssignmentByComma; +- (NSMutableDictionary*)m3u_attributesFromAssignmentByBlank; -- (NSString *)m3u_stringByTrimmingQuoteMark; +- (NSString*)m3u_stringByTrimmingQuoteMark; @end diff --git a/ios/Video/M3U8Kit/Source/NSString+m3u8.m b/ios/Video/M3U8Kit/Source/NSString+m3u8.m index 01d2c4e57c..f93c5bd4ae 100644 --- a/ios/Video/M3U8Kit/Source/NSString+m3u8.m +++ b/ios/Video/M3U8Kit/Source/NSString+m3u8.m @@ -5,33 +5,33 @@ // Created by Oneday on 13-1-11. // Copyright (c) 2013年 0day. All rights reserved. // -//The MIT License (MIT) +// The MIT License (MIT) // -//Copyright (c) 2015 Sun Jin <jeansunvf@gmail.com> +// Copyright (c) 2015 Sun Jin <jeansunvf@gmail.com> // -//Permission is hereby granted, free of charge, to any person obtaining a copy -//of this software and associated documentation files (the "Software"), to deal -//in the Software without restriction, including without limitation the rights -//to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -//copies of the Software, and to permit persons to whom the Software is -//furnished to do so, subject to the following conditions: +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: // -//The above copyright notice and this permission notice shall be included in all -//copies or substantial portions of the Software. +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. // -//THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -//IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -//FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -//AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -//LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -//OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -//SOFTWARE. +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. -#import "NSString+m3u8.h" -#import "M3U8SegmentInfo.h" -#import "M3U8SegmentInfoList.h" #import "M3U8ExtXStreamInf.h" #import "M3U8ExtXStreamInfList.h" +#import "M3U8SegmentInfo.h" +#import "M3U8SegmentInfoList.h" +#import "NSString+m3u8.h" #import "M3U8TagsAndAttributes.h" #import "NSArray+m3u8.h" @@ -42,128 +42,127 @@ @implementation NSString (m3u8) The Extended M3U file format defines two tags: EXTM3U and EXTINF. An Extended M3U file is distinguished from a basic M3U file by its first line, which MUST be #EXTM3U. - + reference url:http://tools.ietf.org/html/draft-pantos-http-live-streaming-00 */ - (BOOL)m3u_isExtendedM3Ufile { - return [self hasPrefix:M3U8_EXTM3U]; + return [self hasPrefix:M3U8_EXTM3U]; } - (BOOL)m3u_isMasterPlaylist { - BOOL isM3U = [self m3u_isExtendedM3Ufile]; - if (isM3U) { - NSRange r1 = [self rangeOfString:M3U8_EXT_X_STREAM_INF]; - NSRange r2 = [self rangeOfString:M3U8_EXT_X_I_FRAME_STREAM_INF]; - if (r1.location != NSNotFound || r2.location != NSNotFound) { - return YES; - } + BOOL isM3U = [self m3u_isExtendedM3Ufile]; + if (isM3U) { + NSRange r1 = [self rangeOfString:M3U8_EXT_X_STREAM_INF]; + NSRange r2 = [self rangeOfString:M3U8_EXT_X_I_FRAME_STREAM_INF]; + if (r1.location != NSNotFound || r2.location != NSNotFound) { + return YES; } - return NO; + } + return NO; } - (BOOL)m3u_isMediaPlaylist { - BOOL isM3U = [self m3u_isExtendedM3Ufile]; - if (isM3U) { - NSRange r = [self rangeOfString:M3U8_EXTINF]; - if (r.location != NSNotFound) { - return YES; - } + BOOL isM3U = [self m3u_isExtendedM3Ufile]; + if (isM3U) { + NSRange r = [self rangeOfString:M3U8_EXTINF]; + if (r.location != NSNotFound) { + return YES; } - return NO; + } + return NO; } -- (M3U8SegmentInfoList *)m3u_segementInfoListValueRelativeToURL:(NSString *)baseURL { - // self == @"" - if (0 == self.length) - return nil; - - /** - The Extended M3U file format defines two tags: EXTM3U and EXTINF. An - Extended M3U file is distinguished from a basic M3U file by its first - line, which MUST be #EXTM3U. - - reference url:http://tools.ietf.org/html/draft-pantos-http-live-streaming-00 - */ - NSRange rangeOfEXTM3U = [self rangeOfString:M3U8_EXTM3U]; - if (rangeOfEXTM3U.location == NSNotFound || - rangeOfEXTM3U.location != 0) { - return nil; +- (M3U8SegmentInfoList*)m3u_segementInfoListValueRelativeToURL:(NSString*)baseURL { + // self == @"" + if (0 == self.length) + return nil; + + /** + The Extended M3U file format defines two tags: EXTM3U and EXTINF. An + Extended M3U file is distinguished from a basic M3U file by its first + line, which MUST be #EXTM3U. + + reference url:http://tools.ietf.org/html/draft-pantos-http-live-streaming-00 + */ + NSRange rangeOfEXTM3U = [self rangeOfString:M3U8_EXTM3U]; + if (rangeOfEXTM3U.location == NSNotFound || rangeOfEXTM3U.location != 0) { + return nil; + } + + M3U8SegmentInfoList* segmentInfoList = [[M3U8SegmentInfoList alloc] init]; + + NSRange segmentRange = [self rangeOfString:M3U8_EXTINF]; + NSString* remainingSegments = self; + + while (NSNotFound != segmentRange.location) { + NSMutableDictionary* params = [[NSMutableDictionary alloc] init]; + if (baseURL) { + [params setObject:baseURL forKey:M3U8_BASE_URL]; } - - M3U8SegmentInfoList *segmentInfoList = [[M3U8SegmentInfoList alloc] init]; - - NSRange segmentRange = [self rangeOfString:M3U8_EXTINF]; - NSString *remainingSegments = self; - - while (NSNotFound != segmentRange.location) { - NSMutableDictionary *params = [[NSMutableDictionary alloc] init]; - if (baseURL) { - [params setObject:baseURL forKey:M3U8_BASE_URL]; - } - - // Read the EXTINF number between #EXTINF: and the comma - NSRange commaRange = [remainingSegments rangeOfString:@","]; - NSRange valueRange = NSMakeRange(segmentRange.location + 8, commaRange.location - (segmentRange.location + 8)); - if (commaRange.location == NSNotFound || valueRange.location > remainingSegments.length -1) - break; - - NSString *value = [remainingSegments substringWithRange:valueRange]; - [params setValue:value forKey:M3U8_EXTINF_DURATION]; - - // ignore the #EXTINF line - remainingSegments = [remainingSegments substringFromIndex:segmentRange.location]; - NSRange extinfoLFRange = [remainingSegments rangeOfString:@"\n"]; - remainingSegments = [remainingSegments substringFromIndex:extinfoLFRange.location + 1]; - - // Read the segment link, and ignore line start with # && blank line - while (1) { - NSRange lfRange = [remainingSegments rangeOfString:@"\n"]; - NSString *line = [remainingSegments substringWithRange:NSMakeRange(0, lfRange.location)]; - line = [line stringByReplacingOccurrencesOfString:@" " withString:@""]; - - remainingSegments = [remainingSegments substringFromIndex:lfRange.location + 1]; - - if ([line characterAtIndex:0] != '#' && 0 != line.length) { - // remove the CR character '\r' - unichar lastChar = [line characterAtIndex:line.length - 1]; - if (lastChar == '\r') { - line = [line substringToIndex:line.length - 1]; - } - - [params setValue:line forKey:M3U8_EXTINF_URI]; - break; - } - } - - M3U8SegmentInfo *segment = [[M3U8SegmentInfo alloc] initWithDictionary:params]; - if (segment) { - [segmentInfoList addSegementInfo:segment]; + + // Read the EXTINF number between #EXTINF: and the comma + NSRange commaRange = [remainingSegments rangeOfString:@","]; + NSRange valueRange = NSMakeRange(segmentRange.location + 8, commaRange.location - (segmentRange.location + 8)); + if (commaRange.location == NSNotFound || valueRange.location > remainingSegments.length - 1) + break; + + NSString* value = [remainingSegments substringWithRange:valueRange]; + [params setValue:value forKey:M3U8_EXTINF_DURATION]; + + // ignore the #EXTINF line + remainingSegments = [remainingSegments substringFromIndex:segmentRange.location]; + NSRange extinfoLFRange = [remainingSegments rangeOfString:@"\n"]; + remainingSegments = [remainingSegments substringFromIndex:extinfoLFRange.location + 1]; + + // Read the segment link, and ignore line start with # && blank line + while (1) { + NSRange lfRange = [remainingSegments rangeOfString:@"\n"]; + NSString* line = [remainingSegments substringWithRange:NSMakeRange(0, lfRange.location)]; + line = [line stringByReplacingOccurrencesOfString:@" " withString:@""]; + + remainingSegments = [remainingSegments substringFromIndex:lfRange.location + 1]; + + if ([line characterAtIndex:0] != '#' && 0 != line.length) { + // remove the CR character '\r' + unichar lastChar = [line characterAtIndex:line.length - 1]; + if (lastChar == '\r') { + line = [line substringToIndex:line.length - 1]; } - - segmentRange = [remainingSegments rangeOfString:M3U8_EXTINF]; + + [params setValue:line forKey:M3U8_EXTINF_URI]; + break; + } + } + + M3U8SegmentInfo* segment = [[M3U8SegmentInfo alloc] initWithDictionary:params]; + if (segment) { + [segmentInfoList addSegementInfo:segment]; } - - return segmentInfoList; + + segmentRange = [remainingSegments rangeOfString:M3U8_EXTINF]; + } + + return segmentInfoList; } -- (NSString *)m3u_stringByTrimmingQuoteMark { - NSCharacterSet *quoteMarkCharactersSet = [NSCharacterSet characterSetWithCharactersInString:@"\"' "]; - NSString *string = [self stringByTrimmingCharactersInSet:quoteMarkCharactersSet]; - return string; +- (NSString*)m3u_stringByTrimmingQuoteMark { + NSCharacterSet* quoteMarkCharactersSet = [NSCharacterSet characterSetWithCharactersInString:@"\"' "]; + NSString* string = [self stringByTrimmingCharactersInSet:quoteMarkCharactersSet]; + return string; } -- (NSMutableDictionary *)m3u_attributesFromAssignmentByComma { - return [self m3u_attributesFromAssignmentByMark:@","]; +- (NSMutableDictionary*)m3u_attributesFromAssignmentByComma { + return [self m3u_attributesFromAssignmentByMark:@","]; } -- (NSMutableDictionary *)m3u_attributesFromAssignmentByBlank { - return [self m3u_attributesFromAssignmentByMark:@" "]; +- (NSMutableDictionary*)m3u_attributesFromAssignmentByBlank { + return [self m3u_attributesFromAssignmentByMark:@" "]; } -- (NSMutableDictionary *)m3u_attributesFromAssignmentByMark:(NSString *)mark { - NSArray<NSString *> *keyValues = [self componentsSeparatedByString:mark]; - - return [keyValues m3u_attributesFromAssignmentByMark:mark]; +- (NSMutableDictionary*)m3u_attributesFromAssignmentByMark:(NSString*)mark { + NSArray<NSString*>* keyValues = [self componentsSeparatedByString:mark]; + + return [keyValues m3u_attributesFromAssignmentByMark:mark]; } @end diff --git a/ios/Video/M3U8Kit/Source/NSURL+m3u8.h b/ios/Video/M3U8Kit/Source/NSURL+m3u8.h index a489059dcf..0b715cd5aa 100644 --- a/ios/Video/M3U8Kit/Source/NSURL+m3u8.h +++ b/ios/Video/M3U8Kit/Source/NSURL+m3u8.h @@ -4,27 +4,27 @@ // // Created by Frank on 16/06/2017. // -//The MIT License (MIT) +// The MIT License (MIT) // -//Copyright (c) 2015 Sun Jin <jeansunvf@gmail.com> +// Copyright (c) 2015 Sun Jin <jeansunvf@gmail.com> // -//Permission is hereby granted, free of charge, to any person obtaining a copy -//of this software and associated documentation files (the "Software"), to deal -//in the Software without restriction, including without limitation the rights -//to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -//copies of the Software, and to permit persons to whom the Software is -//furnished to do so, subject to the following conditions: +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: // -//The above copyright notice and this permission notice shall be included in all -//copies or substantial portions of the Software. +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. // -//THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -//IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -//FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -//AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -//LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -//OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -//SOFTWARE. +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. #import <Foundation/Foundation.h> @@ -37,13 +37,13 @@ @return URL */ -- (NSURL *)m3u_realBaseURL; +- (NSURL*)m3u_realBaseURL; /** Load the specific url and get result model with completion block. - + @param completion when the url resource loaded, completion block could get model and detail error; */ -- (void)m3u_loadAsyncCompletion:(void (^)(M3U8PlaylistModel *model, NSError *error))completion; +- (void)m3u_loadAsyncCompletion:(void (^)(M3U8PlaylistModel* model, NSError* error))completion; @end diff --git a/ios/Video/M3U8Kit/Source/NSURL+m3u8.m b/ios/Video/M3U8Kit/Source/NSURL+m3u8.m index af9400ca2f..c8839ac48c 100644 --- a/ios/Video/M3U8Kit/Source/NSURL+m3u8.m +++ b/ios/Video/M3U8Kit/Source/NSURL+m3u8.m @@ -4,64 +4,62 @@ // // Created by Frank on 16/06/2017. // -//The MIT License (MIT) +// The MIT License (MIT) // -//Copyright (c) 2015 Sun Jin <jeansunvf@gmail.com> +// Copyright (c) 2015 Sun Jin <jeansunvf@gmail.com> // -//Permission is hereby granted, free of charge, to any person obtaining a copy -//of this software and associated documentation files (the "Software"), to deal -//in the Software without restriction, including without limitation the rights -//to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -//copies of the Software, and to permit persons to whom the Software is -//furnished to do so, subject to the following conditions: +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: // -//The above copyright notice and this permission notice shall be included in all -//copies or substantial portions of the Software. +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. // -//THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -//IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -//FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -//AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -//LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -//OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -//SOFTWARE. +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. -#import "NSURL+m3u8.h" #import "M3U8PlaylistModel.h" +#import "NSURL+m3u8.h" @implementation NSURL (m3u8) -- (NSURL *)m3u_realBaseURL { - NSURL *baseURL = self.baseURL; - if (!baseURL) { - NSString *string = [self.absoluteString stringByReplacingOccurrencesOfString:self.lastPathComponent withString:@""]; - - baseURL = [NSURL URLWithString:string]; - } - - return baseURL; +- (NSURL*)m3u_realBaseURL { + NSURL* baseURL = self.baseURL; + if (!baseURL) { + NSString* string = [self.absoluteString stringByReplacingOccurrencesOfString:self.lastPathComponent withString:@""]; + + baseURL = [NSURL URLWithString:string]; + } + + return baseURL; } -- (void)m3u_loadAsyncCompletion:(void (^)(M3U8PlaylistModel *model, NSError *error))completion { - dispatch_async(dispatch_get_global_queue(QOS_CLASS_USER_INTERACTIVE, 0), ^{ - NSError *err = nil; - NSString *str = [[NSString alloc] initWithContentsOfURL:self - encoding:NSUTF8StringEncoding error:&err]; - - if (err) { - completion(nil, err); - return; - } - - M3U8PlaylistModel *listModel = [[M3U8PlaylistModel alloc] initWithString:str - originalURL:self baseURL:self.m3u_realBaseURL error:&err]; - if (err) { - completion(nil, err); - return; - } - - completion(listModel, nil); - }); +- (void)m3u_loadAsyncCompletion:(void (^)(M3U8PlaylistModel* model, NSError* error))completion { + dispatch_async(dispatch_get_global_queue(QOS_CLASS_USER_INTERACTIVE, 0), ^{ + NSError* err = nil; + NSString* str = [[NSString alloc] initWithContentsOfURL:self encoding:NSUTF8StringEncoding error:&err]; + + if (err) { + completion(nil, err); + return; + } + + M3U8PlaylistModel* listModel = [[M3U8PlaylistModel alloc] initWithString:str originalURL:self baseURL:self.m3u_realBaseURL error:&err]; + if (err) { + completion(nil, err); + return; + } + + completion(listModel, nil); + }); } @end diff --git a/ios/Video/RCTVideo-Bridging-Header.h b/ios/Video/RCTVideo-Bridging-Header.h index e3492cd937..1c69eeb7a8 100644 --- a/ios/Video/RCTVideo-Bridging-Header.h +++ b/ios/Video/RCTVideo-Bridging-Header.h @@ -1,7 +1,4 @@ -#import "RCTVideoSwiftLog.h" -#import <React/RCTViewManager.h> #import "CurrentVideos.h" -#import "NSURL+m3u8.h" #import "M3U8ExtXByteRange.h" #import "M3U8ExtXKey.h" #import "M3U8ExtXMedia.h" @@ -17,6 +14,9 @@ #import "M3U8TagsAndAttributes.h" #import "NSArray+m3u8.h" #import "NSString+m3u8.h" +#import "NSURL+m3u8.h" +#import "RCTVideoSwiftLog.h" +#import <React/RCTViewManager.h> #if __has_include(<react-native-video/RCTVideoCache.h>) #import "RCTVideoCache.h" diff --git a/ios/Video/RCTVideoManager.m b/ios/Video/RCTVideoManager.m index b80736afe1..63ad899718 100644 --- a/ios/Video/RCTVideoManager.m +++ b/ios/Video/RCTVideoManager.m @@ -68,9 +68,7 @@ @interface RCT_EXTERN_MODULE (RCTVideoManager, RCTViewManager) RCT_EXPORT_VIEW_PROPERTY(onRestoreUserInterfaceForPictureInPictureStop, RCTDirectEventBlock); RCT_EXPORT_VIEW_PROPERTY(onCommandResult, RCTBubblingEventBlock); -RCT_EXTERN_METHOD(getCurrentTime - : (nonnull NSNumber*)rectTag reactTag - : (nonnull NSNumber *)commandId commandId) +RCT_EXTERN_METHOD(getCurrentTime : (nonnull NSNumber*)rectTag reactTag : (nonnull NSNumber*)commandId commandId) RCT_EXPORT_VIEW_PROPERTY(onReceiveAdEvent, RCTDirectEventBlock); RCT_EXPORT_VIEW_PROPERTY(onTextTracks, RCTDirectEventBlock); diff --git a/ios/Video/WeakVideoRef.h b/ios/Video/WeakVideoRef.h index 852b5264de..ea9433d50a 100644 --- a/ios/Video/WeakVideoRef.h +++ b/ios/Video/WeakVideoRef.h @@ -11,7 +11,7 @@ NS_ASSUME_NONNULL_BEGIN @class RCTVideo; @interface WeakVideoRef : NSObject -@property(weak) RCTVideo *video; +@property(weak) RCTVideo* video; @end NS_ASSUME_NONNULL_END From 83cfe3b0077a7da26bf269bc0af7f3037691a5d7 Mon Sep 17 00:00:00 2001 From: Roman Savka <roman.savka@miquido.com> Date: Fri, 14 Jun 2024 10:27:34 +0200 Subject: [PATCH 20/37] JS fixes --- ios/Video/RCTVideoManager.m | 4 ++-- src/Video.tsx | 22 ++++++++++++++-------- src/specs/VideoNativeComponent.ts | 2 ++ src/types/video.ts | 2 ++ 4 files changed, 20 insertions(+), 10 deletions(-) diff --git a/ios/Video/RCTVideoManager.m b/ios/Video/RCTVideoManager.m index 63ad899718..a37e4acaa6 100644 --- a/ios/Video/RCTVideoManager.m +++ b/ios/Video/RCTVideoManager.m @@ -16,8 +16,8 @@ @interface RCT_EXTERN_MODULE (RCTVideoManager, RCTViewManager) RCT_EXPORT_VIEW_PROPERTY(selectedAudioTrack, NSDictionary); RCT_EXPORT_VIEW_PROPERTY(chapters, NSArray); RCT_EXPORT_VIEW_PROPERTY(paused, BOOL); -RCT_EXPORT_VIEW_PROPERTY(masterVideo, NSNumber); -RCT_EXPORT_VIEW_PROPERTY(slaveVideo, NSNumber); +RCT_EXPORT_VIEW_PROPERTY(principalVideo, NSNumber); +RCT_EXPORT_VIEW_PROPERTY(peripheralVideo, NSNumber); RCT_EXPORT_VIEW_PROPERTY(muted, BOOL); RCT_EXPORT_VIEW_PROPERTY(controls, BOOL); RCT_EXPORT_VIEW_PROPERTY(audioOutput, NSString); diff --git a/src/Video.tsx b/src/Video.tsx index 8a52c99a67..f0c4ee492b 100644 --- a/src/Video.tsx +++ b/src/Video.tsx @@ -66,8 +66,8 @@ export interface VideoRef { save: (options: object) => Promise<VideoSaveData>; setVolume: (volume: number) => void; getCurrentTime: () => Promise<number>; - setPrincipalVideoId: () => void; - setPeripheralVideoId: () => void; + setPrincipalVideoId: (principalId: number) => void; + setPeripheralVideoId: (peripheralId: number) => void; } const Video = forwardRef<VideoRef, ReactVideoProps>( @@ -122,6 +122,8 @@ const Video = forwardRef<VideoRef, ReactVideoProps>( const nativeRef = useRef<ComponentRef<VideoComponentType>>(null); const [showPoster, setShowPoster] = useState(!!poster); const [isFullscreen, setIsFullscreen] = useState(fullscreen); + const [getPrincipalVideo, setPrincipalVideo] = useState(principalVideo); + const [getPeripheralVideo, setPeripheralVideo] = useState(peripheralVideo); const [ _restoreUserInterfaceForPIPStopCompletionHandler, setRestoreUserInterfaceForPIPStopCompletionHandler, @@ -304,12 +306,12 @@ const Video = forwardRef<VideoRef, ReactVideoProps>( return VideoManager.setVolume(volume, getReactTag(nativeRef)); }, []); - const setPrincipalVideoId = useCallback((principalId: string) => { - nativeRef.current?.setNativeProps({principalVideo: principalId}); + const setPrincipalVideoId = useCallback((principalId: number) => { + setPrincipalVideo(principalId); }, []); - const setPeripheralVideoId = useCallback((peripheralId: string) => { - nativeRef.current?.setNativeProps({peripheralVideo: peripheralId}); + const setPeripheralVideoId = useCallback((peripheralId: number) => { + setPeripheralVideo(peripheralId); }, []); const onVideoLoadStart = useCallback( @@ -528,6 +530,8 @@ const Video = forwardRef<VideoRef, ReactVideoProps>( pause, resume, getCurrentTime, + setPrincipalVideoId, + setPeripheralVideoId, restoreUserInterfaceForPictureInPictureStopCompleted, setVolume, }), @@ -539,6 +543,8 @@ const Video = forwardRef<VideoRef, ReactVideoProps>( pause, resume, getCurrentTime, + setPrincipalVideoId, + setPeripheralVideoId, restoreUserInterfaceForPictureInPictureStopCompleted, setVolume, ], @@ -551,8 +557,8 @@ const Video = forwardRef<VideoRef, ReactVideoProps>( {...rest} src={src} drm={_drm} - principalVideo={principalVideo} - peripheralVideo={peripheralVideo} + principalVideo={getPrincipalVideo} + peripheralVideo={getPeripheralVideo} style={StyleSheet.absoluteFill} resizeMode={resizeMode} fullscreen={isFullscreen} diff --git a/src/specs/VideoNativeComponent.ts b/src/specs/VideoNativeComponent.ts index 1f2672d69c..64a34536d2 100644 --- a/src/specs/VideoNativeComponent.ts +++ b/src/specs/VideoNativeComponent.ts @@ -342,6 +342,8 @@ export interface VideoNativeProps extends ViewProps { restoreUserInterfaceForPIPStopCompletionHandler?: boolean; localSourceEncryptionKeyScheme?: string; cookiePolicy: string; + principalVideo: Int32; + peripheralVideo: Int32; debug?: DebugConfig; showNotificationControls?: WithDefault<boolean, false>; // Android, iOS bufferConfig?: BufferConfig; // Android diff --git a/src/types/video.ts b/src/types/video.ts index 92ecf2ab95..e85054eadb 100644 --- a/src/types/video.ts +++ b/src/types/video.ts @@ -269,6 +269,8 @@ export interface ReactVideoProps extends ReactVideoEvents, ViewProps { volume?: number; localSourceEncryptionKeyScheme?: string; cookiePolicy: EnumValues<CookiePolicy>; + principalVideo: number; + peripheralVideo: number; debug?: DebugConfig; allowsExternalPlayback?: boolean; // iOS controlsStyles?: ControlsStyles; // Android From 6cec1c7b61787756e42029ca10cc0c132cea661e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pawel=20Lozi=C5=84ski?= <pawel.lozinski@miquido.com> Date: Fri, 14 Jun 2024 19:19:11 +0200 Subject: [PATCH 21/37] fix swift errors --- ios/.swiftformat | 2 -- ios/Video/RCTVideo.swift | 14 ++++++++------ 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/ios/.swiftformat b/ios/.swiftformat index f18154f318..f9abda7449 100644 --- a/ios/.swiftformat +++ b/ios/.swiftformat @@ -10,7 +10,5 @@ --enable markTypes ---enable isEmpty - --funcattributes "prev-line" --maxwidth 160 \ No newline at end of file diff --git a/ios/Video/RCTVideo.swift b/ios/Video/RCTVideo.swift index bd9e5b80d4..97b962bf89 100644 --- a/ios/Video/RCTVideo.swift +++ b/ios/Video/RCTVideo.swift @@ -112,6 +112,10 @@ class RCTVideo: UIView, RCTVideoPlayerViewControllerDelegate, RCTPlayerObserverH private var _resouceLoaderDelegate: RCTResourceLoaderDelegate? private var _playerObserver: RCTPlayerObserver = .init() + #if USE_VIDEO_CACHING + private let _videoCache: RCTVideoCachingHandler = .init() + #endif + #if os(iOS) private var _pip: RCTPictureInPicture? #endif @@ -683,15 +687,13 @@ class RCTVideo: UIView, RCTVideoPlayerViewControllerDelegate, RCTPlayerObserverH model: M3U8PlaylistModel, principalModel: M3U8PlaylistModel ) -> [String: Any] { - if !model.mainMediaPl.segmentList.isEmpty { + // swiftlint:disable:next empty_count + if !(model.mainMediaPl.segmentList.count == 0) { let uri: URL = model.mainMediaPl.segmentList.segmentInfo(at: 0).uri - if uri == nil { - return .init() - } var codecs = "" - - if !principalModel.masterPlaylist.xStreamList.isEmpty { + // swiftlint:disable:next empty_count + if !(principalModel.masterPlaylist.xStreamList.count == 0) { if let inf = principalModel.masterPlaylist.xStreamList.xStreamInf(at: 0) { codecs = (inf.codecs as NSArray).componentsJoined(by: ",") } From 5ea059cc341bb4dad489e1befae36ba5483010d7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pawel=20Lozi=C5=84ski?= <pawel.lozinski@miquido.com> Date: Fri, 14 Jun 2024 21:53:12 +0200 Subject: [PATCH 22/37] Revert "from:" This reverts commit 3a903b207c62342e98c99c6e30b2af42c705f4e3. --- ios/Video/RCTVideo.swift | 18 ------------------ ios/Video/RCTVideoManager.m | 4 ---- ios/Video/RCTVideoManager.swift | 7 ------- 3 files changed, 29 deletions(-) diff --git a/ios/Video/RCTVideo.swift b/ios/Video/RCTVideo.swift index 97b962bf89..ea873e00d6 100644 --- a/ios/Video/RCTVideo.swift +++ b/ios/Video/RCTVideo.swift @@ -146,7 +146,6 @@ class RCTVideo: UIView, RCTVideoPlayerViewControllerDelegate, RCTPlayerObserverH @objc var onPictureInPictureStatusChanged: RCTDirectEventBlock? @objc var onRestoreUserInterfaceForPictureInPictureStop: RCTDirectEventBlock? @objc var onGetLicense: RCTDirectEventBlock? - @objc var onCommandResult: RCTDirectEventBlock? @objc var onReceiveAdEvent: RCTDirectEventBlock? @objc var onTextTracks: RCTDirectEventBlock? @objc var onAudioTracks: RCTDirectEventBlock? @@ -1397,23 +1396,6 @@ class RCTVideo: UIView, RCTVideoPlayerViewControllerDelegate, RCTPlayerObserverH _resouceLoaderDelegate?.setLicenseResultError(error, licenseUrl) } - func requestedCurrentTime(_ requestId: NSNumber!) { - if let onCommandResult { - return - } - - let result: [NSString: NSNumber] = [ - "requestId": requestId, - "result": getCurrentTime(), - ] - onCommandResult?(result) - } - - private func getCurrentTime() -> NSNumber { - let time = _playerItem != nil ? CMTimeGetSeconds(_playerItem?.currentTime() ?? .zero) : 0 - return NSNumber(value: time) - } - func dismissFullscreenPlayer() { setFullscreen(false) } diff --git a/ios/Video/RCTVideoManager.m b/ios/Video/RCTVideoManager.m index a37e4acaa6..56cbfdc9a9 100644 --- a/ios/Video/RCTVideoManager.m +++ b/ios/Video/RCTVideoManager.m @@ -66,10 +66,6 @@ @interface RCT_EXTERN_MODULE (RCTVideoManager, RCTViewManager) RCT_EXPORT_VIEW_PROPERTY(onGetLicense, RCTDirectEventBlock); RCT_EXPORT_VIEW_PROPERTY(onPictureInPictureStatusChanged, RCTDirectEventBlock); RCT_EXPORT_VIEW_PROPERTY(onRestoreUserInterfaceForPictureInPictureStop, RCTDirectEventBlock); - -RCT_EXPORT_VIEW_PROPERTY(onCommandResult, RCTBubblingEventBlock); -RCT_EXTERN_METHOD(getCurrentTime : (nonnull NSNumber*)rectTag reactTag : (nonnull NSNumber*)commandId commandId) - RCT_EXPORT_VIEW_PROPERTY(onReceiveAdEvent, RCTDirectEventBlock); RCT_EXPORT_VIEW_PROPERTY(onTextTracks, RCTDirectEventBlock); RCT_EXPORT_VIEW_PROPERTY(onAudioTracks, RCTDirectEventBlock); diff --git a/ios/Video/RCTVideoManager.swift b/ios/Video/RCTVideoManager.swift index 5e34a684d4..d8053f37ea 100644 --- a/ios/Video/RCTVideoManager.swift +++ b/ios/Video/RCTVideoManager.swift @@ -63,13 +63,6 @@ class RCTVideoManager: RCTViewManager { }) } - @objc(getCurrentTime:commandId:) - func getCurrentTime(_ reactTag: NSNumber, commandId: NSNumber) { - performOnVideoView(withReactTag: reactTag, callback: { videoView in - videoView?.requestedCurrentTime(commandId) - }) - } - @objc(presentFullscreenPlayer:) func presentFullscreenPlayer(_ reactTag: NSNumber) { performOnVideoView(withReactTag: reactTag, callback: { videoView in From 1b96ed6025d65484ddab7391366c8c97ea788938 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pawel=20Lozi=C5=84ski?= <pawel.lozinski@miquido.com> Date: Fri, 14 Jun 2024 23:59:41 +0200 Subject: [PATCH 23/37] Replace onPlayedTracksChange with 3 callbacks. --- ios/Video/RCTVideo.swift | 59 ++++++++++++++++++++++++------------- ios/Video/RCTVideoManager.m | 1 + 2 files changed, 40 insertions(+), 20 deletions(-) diff --git a/ios/Video/RCTVideo.swift b/ios/Video/RCTVideo.swift index ea873e00d6..8a3c9fbece 100644 --- a/ios/Video/RCTVideo.swift +++ b/ios/Video/RCTVideo.swift @@ -139,7 +139,6 @@ class RCTVideo: UIView, RCTVideoPlayerViewControllerDelegate, RCTPlayerObserverH @objc var onPlaybackStalled: RCTDirectEventBlock? @objc var onPlaybackResume: RCTDirectEventBlock? @objc var onPlaybackRateChange: RCTDirectEventBlock? - @objc var onPlayedTracksChange: RCTDirectEventBlock? @objc var onVolumeChange: RCTDirectEventBlock? @objc var onVideoPlaybackStateChanged: RCTDirectEventBlock? @objc var onVideoExternalPlaybackChange: RCTDirectEventBlock? @@ -149,6 +148,7 @@ class RCTVideo: UIView, RCTVideoPlayerViewControllerDelegate, RCTPlayerObserverH @objc var onReceiveAdEvent: RCTDirectEventBlock? @objc var onTextTracks: RCTDirectEventBlock? @objc var onAudioTracks: RCTDirectEventBlock? + @objc var onVideoTracks: RCTDirectEventBlock? @objc var onTextTrackDataChanged: RCTDirectEventBlock? @objc @@ -1530,25 +1530,44 @@ class RCTVideo: UIView, RCTVideoPlayerViewControllerDelegate, RCTPlayerObserverH } func handleMetadataUpdateForTrackChange() { - if onPlayedTracksChange != nil { - let urlString: String = _player?.currentItem?.accessLog()?.events.last?.uri ?? "" - let url = NSURL(string: urlString) - let asset: AVAsset? = _player?.currentItem?.asset - - let principalURL: NSURL? = (_player?.currentItem?.asset as? AVURLAsset)?.url as? NSURL - - principalURL?.m3u_loadAsyncCompletion { principalModel, _ in - if let url { - url.m3u_loadAsyncCompletion { model, _ in - if let model, let principalModel { - self.onPlayedTracksChange?( - [ - "audioTrack": self.getAudioTrackInfo(model: model, principalModel: principalModel), - "textTrack": self._textTracks, - "videoTrack": self.getVideoTrackInfo(model: model, principalModel: principalModel), - ] - ) - } + if onTextTracks != nil { + self.onTextTracks?( + [ + "textTrack": self._textTracks, + ] + ) + } + + guard let player = _player, + let urlString = _player?.currentItem?.accessLog()?.events.last?.uri, + let url = NSURL(string: urlString), + let principalURL: NSURL = (_player?.currentItem?.asset as? AVURLAsset)?.url as? NSURL else { + return + } + + principalURL.m3u_loadAsyncCompletion { principalModel, _ in + url.m3u_loadAsyncCompletion { model, _ in + if let model, let principalModel { + if self.onAudioTracks != nil { + self.onAudioTracks?( + [ + "audioTrack": self.getAudioTrackInfo( + model: model, + principalModel: principalModel + ), + ] + ) + } + + if self.onVideoTracks != nil { + self.onVideoTracks?( + [ + "videoTrack": self.getVideoTrackInfo( + model: model, + principalModel: principalModel + ), + ] + ) } } } diff --git a/ios/Video/RCTVideoManager.m b/ios/Video/RCTVideoManager.m index 56cbfdc9a9..e7e812903a 100644 --- a/ios/Video/RCTVideoManager.m +++ b/ios/Video/RCTVideoManager.m @@ -69,6 +69,7 @@ @interface RCT_EXTERN_MODULE (RCTVideoManager, RCTViewManager) RCT_EXPORT_VIEW_PROPERTY(onReceiveAdEvent, RCTDirectEventBlock); RCT_EXPORT_VIEW_PROPERTY(onTextTracks, RCTDirectEventBlock); RCT_EXPORT_VIEW_PROPERTY(onAudioTracks, RCTDirectEventBlock); +RCT_EXPORT_VIEW_PROPERTY(onVideoTracks, RCTDirectEventBlock); RCT_EXPORT_VIEW_PROPERTY(onTextTrackDataChanged, RCTDirectEventBlock); RCT_EXTERN_METHOD(save From 4014fe72afda024e8e2fdaa53fb967c16228a646 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pawel=20Lozi=C5=84ski?= <pawel.lozinski@miquido.com> Date: Sun, 16 Jun 2024 20:51:52 +0200 Subject: [PATCH 24/37] Update changelog. --- CHANGELOG.md | 41 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 41 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index f6a4851953..5dc3cc40b1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,44 @@ +# [6.0.0] ?? + +### Features + +* **ios:** XHUB-5327 - Expose channel (HLS) and Dolby SupplementalProperty (DASH) values from currently played resource to ReactNative + ([675207f](https://github.com/miquido/react-native-video/commit/675207f6dbe7b817bb2c19292a9c487406b36ea1) + ([a6823bb])(https://github.com/miquido/react-native-video/commit/a6823bbca0cfa135b995bd2117f18b6834ba20ad) +* **ios:** Send on played tracks change + ([69cd4f0])(https://github.com/miquido/react-native-video/commit/69cd4f0d26ff2012cb5cd7f49f2da12b566333d3) +* **ios:** Attempt to sync two videos. + ([300794d])(https://github.com/miquido/react-native-video/commit/300794dd73d36c6722106ad6ec3399a580d3c407) +* **ios: Send current time request and handle command result on iOS. + ([dbba295])(https://github.com/miquido/react-native-video/commit/dbba295e783f8f9f667346640e1df6b78a63c474) + +* **ios:** Fix displaying string uri + ([320985f])(https://github.com/miquido/react-native-video/commit/320985f7afef60a3a1e647dc02df1b5630ac7359) +* **ios:** Removed unused method + ([ce29859])(https://github.com/miquido/react-native-video/commit/ce2985978d036fe0685355c6a760157603243acb) +* **ios:** Add proper audio parsing + ([2c55b87])(https://github.com/miquido/react-native-video/commit/2c55b87d693cfe39651086811ebaf6a61a0c51da) +* **ios:** Fix autoplay & playing after buffering + ([8a15097])(https://github.com/miquido/react-native-video/commit/8a150978234cdb2efbdd1a84dcab29fef09c08f8) +* **ios:** Adding Dolby Atmos workaround. + ([3030ddd])(https://github.com/miquido/react-native-video/commit/3030ddd01a9a50b730ec0af13dc0650296347402) +* **ios:** Fix for tvOS native audio menu language selector + ([a42240d])(https://github.com/miquido/react-native-video/commit/a42240d5543cbc9f7c11a0692c05d625d345457a) + + + + + +### Bug Fixes + +* **android:** prevent changing video track when video load ([#3683](https://github.com/TheWidlarzGroup/react-native-video/issues/3683)) ([6f61d7f](https://github.com/TheWidlarzGroup/react-native-video/commit/6f61d7f6e6969d05e4cee9bdb2e4cbc80d356e7f)) +* **android:** video flickering add playback start ([#3746](https://github.com/TheWidlarzGroup/react-native-video/issues/3746)) ([b1cd52b](https://github.com/TheWidlarzGroup/react-native-video/commit/b1cd52bc58b3dfd02dab4784ea423ebddae874c4)) +* avoid crash when setting index to 0 to tracks selection ([#3721](https://github.com/TheWidlarzGroup/react-native-video/issues/3721)) ([518a9a9](https://github.com/TheWidlarzGroup/react-native-video/commit/518a9a93e06686ba707427078a1770dc3d803b2b)) +* **ios:** destroy adsManager when player detach from super view ([#3716](https://github.com/TheWidlarzGroup/react-native-video/issues/3716)) ([#3722](https://github.com/TheWidlarzGroup/react-native-video/issues/3722)) ([e96c173](https://github.com/TheWidlarzGroup/react-native-video/commit/e96c17321f1347818c1f5a38628d65b5b4bd5e7b)) +* **ios:** ensure duration available when playing live ([#3710](https://github.com/TheWidlarzGroup/react-native-video/issues/3710)) ([d56b251](https://github.com/TheWidlarzGroup/react-native-video/commit/d56b251aef6d4ca1708c7bbada15016efbf12caf)) +* **ios:** ensure orientation is correct on iOS ([#3719](https://github.com/TheWidlarzGroup/react-native-video/issues/3719)) ([1a8295c](https://github.com/TheWidlarzGroup/react-native-video/commit/1a8295c8bf30d53135d723fc9aface1a812be78a)) +* **ios:** fix text track selection by index ([#3728](https://github.com/TheWidlarzGroup/react-native-video/issues/3728)) ([51e22ab](https://github.com/TheWidlarzGroup/react-native-video/commit/51e22abfe35978ee3fd1a7b3dc6f6c769d1b24bc)) + # [6.1.2+dolbyxp.1.0](https://github.com/miquido/react-native-video/compare/6.0.0+dolbyxp.1.0...miquido:react-native-video:6.1.2+dolbyxp.1.0) From bb82c678a0dfe4f10981960f92b6bc1eb3148ddb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pawel=20Lozi=C5=84ski?= <pawel.lozinski@miquido.com> Date: Sun, 16 Jun 2024 22:24:30 +0200 Subject: [PATCH 25/37] Update docs. --- docs/pages/component/methods.mdx | 12 ++++++++++++ docs/pages/component/props.mdx | 12 ++++++++++++ 2 files changed, 24 insertions(+) diff --git a/docs/pages/component/methods.mdx b/docs/pages/component/methods.mdx index 4c69b71a73..17f8b6292f 100644 --- a/docs/pages/component/methods.mdx +++ b/docs/pages/component/methods.mdx @@ -4,6 +4,18 @@ import PlatformsList from '../../components/PlatformsList/PlatformsList.tsx'; This page shows the list of available methods +### `setPrincipalVideoId` + +<PlatformsList types={['iOS']} /> + +Method is used to assign an ID to the principal video for synchronization purposes. + +### `setPeripheralVideoId` + +<PlatformsList types={['iOS']} /> + +Method is used to assign an ID to the peripheral video for synchronization purposes. + ### `dismissFullscreenPlayer` <PlatformsList types={['Android', 'iOS']} /> diff --git a/docs/pages/component/props.mdx b/docs/pages/component/props.mdx index 4e4133d22c..af84593188 100644 --- a/docs/pages/component/props.mdx +++ b/docs/pages/component/props.mdx @@ -6,6 +6,18 @@ This page shows the list of available properties to configure player ## Details +### `principalVideo` + +<PlatformsList types={['iOS']} /> + +The principal video property denotes the primary video source used for synchronization purposes. + +### `peripheralVideo` + +<PlatformsList types={['iOS']} /> + +The peripheral video property denotes the secondary video source used for synchronization purposes. + ### `adTagUrl` <PlatformsList types={['Android', 'iOS']} /> From 7eecb127959b12b42b66b8db91e1a05f1e3be4f9 Mon Sep 17 00:00:00 2001 From: Roman Savka <roman.savka@miquido.com> Date: Fri, 14 Jun 2024 10:44:58 +0200 Subject: [PATCH 26/37] (android): fix cookiePolicy property typo --- CHANGELOG.md | 14 ++++---------- .../brentvatne/exoplayer/ReactExoplayerView.java | 8 ++++---- .../exoplayer/ReactExoplayerViewManager.java | 8 ++++---- 3 files changed, 12 insertions(+), 18 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5dc3cc40b1..2ff6f62a5e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -24,21 +24,15 @@ ([3030ddd])(https://github.com/miquido/react-native-video/commit/3030ddd01a9a50b730ec0af13dc0650296347402) * **ios:** Fix for tvOS native audio menu language selector ([a42240d])(https://github.com/miquido/react-native-video/commit/a42240d5543cbc9f7c11a0692c05d625d345457a) - - + +# [6.1.2+dolbyxp.1.1](https://github.com/miquido/react-native-video/compare/6.1.2+dolbyxp.1.0...miquido:react-native-video:6.1.2+dolbyxp.1.1) ### Bug Fixes -* **android:** prevent changing video track when video load ([#3683](https://github.com/TheWidlarzGroup/react-native-video/issues/3683)) ([6f61d7f](https://github.com/TheWidlarzGroup/react-native-video/commit/6f61d7f6e6969d05e4cee9bdb2e4cbc80d356e7f)) -* **android:** video flickering add playback start ([#3746](https://github.com/TheWidlarzGroup/react-native-video/issues/3746)) ([b1cd52b](https://github.com/TheWidlarzGroup/react-native-video/commit/b1cd52bc58b3dfd02dab4784ea423ebddae874c4)) -* avoid crash when setting index to 0 to tracks selection ([#3721](https://github.com/TheWidlarzGroup/react-native-video/issues/3721)) ([518a9a9](https://github.com/TheWidlarzGroup/react-native-video/commit/518a9a93e06686ba707427078a1770dc3d803b2b)) -* **ios:** destroy adsManager when player detach from super view ([#3716](https://github.com/TheWidlarzGroup/react-native-video/issues/3716)) ([#3722](https://github.com/TheWidlarzGroup/react-native-video/issues/3722)) ([e96c173](https://github.com/TheWidlarzGroup/react-native-video/commit/e96c17321f1347818c1f5a38628d65b5b4bd5e7b)) -* **ios:** ensure duration available when playing live ([#3710](https://github.com/TheWidlarzGroup/react-native-video/issues/3710)) ([d56b251](https://github.com/TheWidlarzGroup/react-native-video/commit/d56b251aef6d4ca1708c7bbada15016efbf12caf)) -* **ios:** ensure orientation is correct on iOS ([#3719](https://github.com/TheWidlarzGroup/react-native-video/issues/3719)) ([1a8295c](https://github.com/TheWidlarzGroup/react-native-video/commit/1a8295c8bf30d53135d723fc9aface1a812be78a)) -* **ios:** fix text track selection by index ([#3728](https://github.com/TheWidlarzGroup/react-native-video/issues/3728)) ([51e22ab](https://github.com/TheWidlarzGroup/react-native-video/commit/51e22abfe35978ee3fd1a7b3dc6f6c769d1b24bc)) - +* **android:** Fix typo for cookiePolicy property. +* Fix typo in CookiePolicy enum. # [6.1.2+dolbyxp.1.0](https://github.com/miquido/react-native-video/compare/6.0.0+dolbyxp.1.0...miquido:react-native-video:6.1.2+dolbyxp.1.0) diff --git a/android/src/main/java/com/brentvatne/exoplayer/ReactExoplayerView.java b/android/src/main/java/com/brentvatne/exoplayer/ReactExoplayerView.java index f80306ff04..02a50e0161 100644 --- a/android/src/main/java/com/brentvatne/exoplayer/ReactExoplayerView.java +++ b/android/src/main/java/com/brentvatne/exoplayer/ReactExoplayerView.java @@ -306,7 +306,7 @@ public void setId(int id) { private void createViews() { clearResumePosition(); mediaDataSourceFactory = buildDataSourceFactory(true); - setCookiesPolicy(CookiesPolicy.SYSTEM_DEFAULT); + setCookiePolicy(CookiesPolicy.SYSTEM_DEFAULT); LayoutParams layoutParams = new LayoutParams( LayoutParams.MATCH_PARENT, @@ -1932,11 +1932,11 @@ public void setControlsStyles(ControlsConfig controlsStyles) { refreshProgressBarVisibility(); } - public void setCookiesPolicy(String policy) { - setCookiesPolicy(CookiesPolicy.valueOf(policy)); + public void setCookiePolicy(String policy) { + setCookiePolicy(CookiesPolicy.valueOf(policy)); } - private void setCookiesPolicy(CookiesPolicy policy) { + private void setCookiePolicy(CookiesPolicy policy) { cookiesPolicy = policy; CookiePolicy value = cookiesPolicy.getValue(); if (value != null) { diff --git a/android/src/main/java/com/brentvatne/exoplayer/ReactExoplayerViewManager.java b/android/src/main/java/com/brentvatne/exoplayer/ReactExoplayerViewManager.java index 01edc5a05f..61ef19407f 100644 --- a/android/src/main/java/com/brentvatne/exoplayer/ReactExoplayerViewManager.java +++ b/android/src/main/java/com/brentvatne/exoplayer/ReactExoplayerViewManager.java @@ -93,7 +93,7 @@ public class ReactExoplayerViewManager extends ViewGroupManager<ReactExoplayerVi private static final String PROP_SUBTITLE_LINES_RESPECTED = "subtitleLinesRespected"; private static final String PROP_SHUTTER_COLOR = "shutterColor"; private static final String PROP_SHOW_NOTIFICATION_CONTROLS = "showNotificationControls"; - private static final String PROP_COOKIES_POLICY = "cookiesPolicy"; + private static final String PROP_COOKIE_POLICY = "cookiePolicy"; private static final String PROP_DEBUG = "debug"; private static final String PROP_CONTROLS_STYLES = "controlsStyles"; @@ -447,9 +447,9 @@ public void setShowNotificationControls(final ReactExoplayerView videoView, fina videoView.setShowNotificationControls(showNotificationControls); } - @ReactProp(name = PROP_COOKIES_POLICY) - public void setCookiesPolicy(final ReactExoplayerView videoView, final String cookiesPolicy) { - videoView.setCookiesPolicy(cookiesPolicy); + @ReactProp(name = PROP_COOKIE_POLICY) + public void setCookiePolicy(final ReactExoplayerView videoView, final String cookiesPolicy) { + videoView.setCookiePolicy(cookiesPolicy); } @ReactProp(name = PROP_DEBUG, defaultBoolean = false) From 38f906af57599a965a32b4f1b606111b2ba872df Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pawel=20Lozi=C5=84ski?= <pawel.lozinski@miquido.com> Date: Mon, 17 Jun 2024 15:23:36 +0200 Subject: [PATCH 27/37] Restore --enable isEmpty swiftformat rule. --- ios/.swiftformat | 2 ++ ios/Video/RCTVideo.swift | 2 ++ 2 files changed, 4 insertions(+) diff --git a/ios/.swiftformat b/ios/.swiftformat index f9abda7449..f18154f318 100644 --- a/ios/.swiftformat +++ b/ios/.swiftformat @@ -10,5 +10,7 @@ --enable markTypes +--enable isEmpty + --funcattributes "prev-line" --maxwidth 160 \ No newline at end of file diff --git a/ios/Video/RCTVideo.swift b/ios/Video/RCTVideo.swift index 8a3c9fbece..bb3cfbd53b 100644 --- a/ios/Video/RCTVideo.swift +++ b/ios/Video/RCTVideo.swift @@ -687,11 +687,13 @@ class RCTVideo: UIView, RCTVideoPlayerViewControllerDelegate, RCTPlayerObserverH principalModel: M3U8PlaylistModel ) -> [String: Any] { // swiftlint:disable:next empty_count + // swiftformat:disable:next isEmpty if !(model.mainMediaPl.segmentList.count == 0) { let uri: URL = model.mainMediaPl.segmentList.segmentInfo(at: 0).uri var codecs = "" // swiftlint:disable:next empty_count + // swiftformat:disable:next isEmpty if !(principalModel.masterPlaylist.xStreamList.count == 0) { if let inf = principalModel.masterPlaylist.xStreamList.xStreamInf(at: 0) { codecs = (inf.codecs as NSArray).componentsJoined(by: ",") From 5281591f918930012971d75dd454a944994ac811 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pawel=20Lozi=C5=84ski?= <pawel.lozinski@miquido.com> Date: Mon, 17 Jun 2024 15:24:07 +0200 Subject: [PATCH 28/37] Remove onPlayedTracksChange. --- ios/Video/RCTVideoManager.m | 1 - 1 file changed, 1 deletion(-) diff --git a/ios/Video/RCTVideoManager.m b/ios/Video/RCTVideoManager.m index e7e812903a..a93b6001ae 100644 --- a/ios/Video/RCTVideoManager.m +++ b/ios/Video/RCTVideoManager.m @@ -59,7 +59,6 @@ @interface RCT_EXTERN_MODULE (RCTVideoManager, RCTViewManager) RCT_EXPORT_VIEW_PROPERTY(onPlaybackStalled, RCTDirectEventBlock); RCT_EXPORT_VIEW_PROPERTY(onPlaybackResume, RCTDirectEventBlock); RCT_EXPORT_VIEW_PROPERTY(onPlaybackRateChange, RCTDirectEventBlock); -RCT_EXPORT_VIEW_PROPERTY(onPlayedTracksChange, RCTDirectEventBlock); RCT_EXPORT_VIEW_PROPERTY(onVolumeChange, RCTDirectEventBlock); RCT_EXPORT_VIEW_PROPERTY(onVideoPlaybackStateChanged, RCTDirectEventBlock); RCT_EXPORT_VIEW_PROPERTY(onVideoExternalPlaybackChange, RCTDirectEventBlock); From 6d63ae0fa437b309fa9b65f5fbb0a4926133fb58 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pawel=20Lozi=C5=84ski?= <pawel.lozinski@miquido.com> Date: Mon, 17 Jun 2024 15:27:56 +0200 Subject: [PATCH 29/37] Update docs with info about iOS platform support. --- docs/pages/component/events.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/pages/component/events.mdx b/docs/pages/component/events.mdx index 6f65e92ac7..fd4507a125 100644 --- a/docs/pages/component/events.mdx +++ b/docs/pages/component/events.mdx @@ -547,7 +547,7 @@ Example: ### `onVideoTracks` -<PlatformsList types={['Android']} /> +<PlatformsList types={['Android', 'iOS']} /> Callback function that is called when video tracks change From 9fd785fdde5a07fd5936b3f4414466a80c1d18dc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pawel=20Lozi=C5=84ski?= <pawel.lozinski@miquido.com> Date: Mon, 17 Jun 2024 15:33:28 +0200 Subject: [PATCH 30/37] Update changelog. --- CHANGELOG.md | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ebf283f8d6..fec5373bfb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,4 +1,5 @@ -# [6.0.0] ?? +# [6.1.2+dolbyxp.1.2](https://github.com/miquido/react-native-video/compare/6.1.2+dolbyxp.1.1...miquido:react-native-video:6.1.2+dolbyxp.1.2) + ### Features @@ -6,12 +7,12 @@ ([675207f](https://github.com/miquido/react-native-video/commit/675207f6dbe7b817bb2c19292a9c487406b36ea1) ([a6823bb])(https://github.com/miquido/react-native-video/commit/a6823bbca0cfa135b995bd2117f18b6834ba20ad) * **ios:** Send on played tracks change - ([69cd4f0])(https://github.com/miquido/react-native-video/commit/69cd4f0d26ff2012cb5cd7f49f2da12b566333d3) + ([1b96ed6])(https://github.com/miquido/react-native-video/pull/25/commits/1b96ed6025d65484ddab7391366c8c97ea788938) * **ios:** Attempt to sync two videos. ([300794d])(https://github.com/miquido/react-native-video/commit/300794dd73d36c6722106ad6ec3399a580d3c407) -* **ios: Send current time request and handle command result on iOS. - ([dbba295])(https://github.com/miquido/react-native-video/commit/dbba295e783f8f9f667346640e1df6b78a63c474) - + +### Bug Fixes + * **ios:** Fix displaying string uri ([320985f])(https://github.com/miquido/react-native-video/commit/320985f7afef60a3a1e647dc02df1b5630ac7359) * **ios:** Removed unused method From 61c4e720cff180e7c02d0ee007acae059b4038d1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pawel=20Lozi=C5=84ski?= <pawel.lozinski@miquido.com> Date: Mon, 17 Jun 2024 16:03:56 +0200 Subject: [PATCH 31/37] Current videos in swift. --- ios/RCTVideo.xcodeproj/project.pbxproj | 12 ++---- ios/Video/CurrentVideos.h | 19 --------- ios/Video/CurrentVideos.m | 54 -------------------------- ios/Video/CurrentVideos.swift | 25 ++++++++++++ ios/Video/RCTVideo-Bridging-Header.h | 1 - ios/Video/RCTVideo.swift | 10 ++--- ios/Video/WeakVideoRef.h | 17 -------- ios/Video/WeakVideoRef.m | 12 ------ ios/Video/WeakVideoRef.swift | 7 ++++ 9 files changed, 41 insertions(+), 116 deletions(-) delete mode 100644 ios/Video/CurrentVideos.h delete mode 100644 ios/Video/CurrentVideos.m create mode 100644 ios/Video/CurrentVideos.swift delete mode 100644 ios/Video/WeakVideoRef.h delete mode 100644 ios/Video/WeakVideoRef.m create mode 100644 ios/Video/WeakVideoRef.swift diff --git a/ios/RCTVideo.xcodeproj/project.pbxproj b/ios/RCTVideo.xcodeproj/project.pbxproj index 6f5365ead8..b6ebeeaef7 100644 --- a/ios/RCTVideo.xcodeproj/project.pbxproj +++ b/ios/RCTVideo.xcodeproj/project.pbxproj @@ -49,10 +49,8 @@ 0177D39827170A7A00F5BE18 /* RCTVideo-Bridging-Header.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = "RCTVideo-Bridging-Header.h"; path = "Video/RCTVideo-Bridging-Header.h"; sourceTree = "<group>"; }; 0177D39927170A7A00F5BE18 /* RCTVideo.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = RCTVideo.swift; path = Video/RCTVideo.swift; sourceTree = "<group>"; }; 134814201AA4EA6300B7C361 /* libRCTVideo.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = libRCTVideo.a; sourceTree = BUILT_PRODUCTS_DIR; }; - DCF8FEFD2C1A174E00CCA6B0 /* CurrentVideos.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = CurrentVideos.h; path = Video/CurrentVideos.h; sourceTree = "<group>"; }; - DCF8FEFE2C1A174E00CCA6B0 /* CurrentVideos.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; name = CurrentVideos.m; path = Video/CurrentVideos.m; sourceTree = "<group>"; }; - DCF8FEFF2C1A18AC00CCA6B0 /* WeakVideoRef.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = WeakVideoRef.h; path = Video/WeakVideoRef.h; sourceTree = "<group>"; }; - DCF8FF002C1A18AC00CCA6B0 /* WeakVideoRef.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; name = WeakVideoRef.m; path = Video/WeakVideoRef.m; sourceTree = "<group>"; }; + DCD62EF02C20781F00669DF5 /* WeakVideoRef.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = WeakVideoRef.swift; path = Video/WeakVideoRef.swift; sourceTree = "<group>"; }; + DCD62EF12C20781F00669DF5 /* CurrentVideos.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = CurrentVideos.swift; path = Video/CurrentVideos.swift; sourceTree = "<group>"; }; DCF8FF322C1AF64800CCA6B0 /* M3U8Kit */ = {isa = PBXFileReference; lastKnownFileType = folder; name = M3U8Kit; path = Video/M3U8Kit; sourceTree = "<group>"; }; /* End PBXFileReference section */ @@ -97,10 +95,8 @@ 01489051272001A100E69940 /* Features */, 0177D39527170A7A00F5BE18 /* RCTSwiftLog */, 0177D39927170A7A00F5BE18 /* RCTVideo.swift */, - DCF8FEFD2C1A174E00CCA6B0 /* CurrentVideos.h */, - DCF8FEFE2C1A174E00CCA6B0 /* CurrentVideos.m */, - DCF8FEFF2C1A18AC00CCA6B0 /* WeakVideoRef.h */, - DCF8FF002C1A18AC00CCA6B0 /* WeakVideoRef.m */, + DCD62EF12C20781F00669DF5 /* CurrentVideos.swift */, + DCD62EF02C20781F00669DF5 /* WeakVideoRef.swift */, 0177D39727170A7A00F5BE18 /* RCTVideoManager.m */, 0177D39227170A7A00F5BE18 /* RCTVideoManager.swift */, 0177D39427170A7A00F5BE18 /* RCTVideoPlayerViewController.swift */, diff --git a/ios/Video/CurrentVideos.h b/ios/Video/CurrentVideos.h deleted file mode 100644 index 887ee158e9..0000000000 --- a/ios/Video/CurrentVideos.h +++ /dev/null @@ -1,19 +0,0 @@ -// -// CurrentVideos.h -// react-native-video -// -// Created by marcin.dziennik on 9/8/23. -// - -#import <Foundation/Foundation.h> - -NS_ASSUME_NONNULL_BEGIN - -@class RCTVideo; -@interface CurrentVideos : NSObject -+ (instancetype)shared; -- (void)add:(RCTVideo*)video forTag:(NSNumber*)tag; -- (nullable RCTVideo*)videoForTag:(NSNumber*)tag; -@end - -NS_ASSUME_NONNULL_END diff --git a/ios/Video/CurrentVideos.m b/ios/Video/CurrentVideos.m deleted file mode 100644 index 6f9d7856ed..0000000000 --- a/ios/Video/CurrentVideos.m +++ /dev/null @@ -1,54 +0,0 @@ -// -// CurrentVideos.m -// react-native-video -// -// Created by marcin.dziennik on 9/8/23. -// - -#import "CurrentVideos.h" -#import "WeakVideoRef.h" - -@interface CurrentVideos () -@property(strong) NSMutableDictionary<NSNumber*, WeakVideoRef*>* videos; -@end - -@implementation CurrentVideos - -+ (nonnull instancetype)shared { - static CurrentVideos* instance = nil; - static dispatch_once_t onceToken; - dispatch_once(&onceToken, ^{ - instance = [[self alloc] init]; - }); - return instance; -} - -- (instancetype)init { - if (self = [super init]) { - _videos = [[NSMutableDictionary alloc] init]; - } - - return self; -} - -- (void)add:(nonnull RCTVideo*)video forTag:(nonnull NSNumber*)tag { - [self cleanup]; - WeakVideoRef* ref = [[WeakVideoRef alloc] init]; - ref.video = video; - [_videos setObject:ref forKey:tag]; -} - -- (nullable RCTVideo*)videoForTag:(nonnull NSNumber*)tag { - [self cleanup]; - return [[_videos objectForKey:tag] video]; -} - -- (void)cleanup { - for (NSNumber* key in [_videos allKeys]) { - if (_videos[key].video == nil) { - [_videos removeObjectForKey:key]; - } - } -} - -@end diff --git a/ios/Video/CurrentVideos.swift b/ios/Video/CurrentVideos.swift new file mode 100644 index 0000000000..87571336c4 --- /dev/null +++ b/ios/Video/CurrentVideos.swift @@ -0,0 +1,25 @@ +class CurrentVideos { + private var videos: [NSNumber: WeakVideoRef] = .init() + + class var sharedInstance: CurrentVideos { + enum Shared { + static let instance = CurrentVideos() + } + return Shared.instance + } + + func add(video: RCTVideo, for tag: NSNumber) { + let ref: WeakVideoRef = .init(video) + videos[tag] = ref + } + + func video(for tag: NSNumber) -> RCTVideo? { + videos[tag]?.video + } + + private func cleanup() { + for key in videos.keys where videos[key]?.video == nil { + videos.removeValue(forKey: key) + } + } +} diff --git a/ios/Video/RCTVideo-Bridging-Header.h b/ios/Video/RCTVideo-Bridging-Header.h index 1c69eeb7a8..219e25cdd6 100644 --- a/ios/Video/RCTVideo-Bridging-Header.h +++ b/ios/Video/RCTVideo-Bridging-Header.h @@ -1,4 +1,3 @@ -#import "CurrentVideos.h" #import "M3U8ExtXByteRange.h" #import "M3U8ExtXKey.h" #import "M3U8ExtXMedia.h" diff --git a/ios/Video/RCTVideo.swift b/ios/Video/RCTVideo.swift index bb3cfbd53b..419265a7b1 100644 --- a/ios/Video/RCTVideo.swift +++ b/ios/Video/RCTVideo.swift @@ -555,7 +555,7 @@ class RCTVideo: UIView, RCTVideoPlayerViewControllerDelegate, RCTPlayerObserverH } self._videoLoadStarted = true - CurrentVideos.shared().add(self, forTag: self.reactTag) + CurrentVideos.sharedInstance.add(video: self, for: self.reactTag) self.applyNextSource() } @@ -686,15 +686,15 @@ class RCTVideo: UIView, RCTVideoPlayerViewControllerDelegate, RCTPlayerObserverH model: M3U8PlaylistModel, principalModel: M3U8PlaylistModel ) -> [String: Any] { - // swiftlint:disable:next empty_count // swiftformat:disable:next isEmpty if !(model.mainMediaPl.segmentList.count == 0) { + // swiftlint:disable:previous empty_count let uri: URL = model.mainMediaPl.segmentList.segmentInfo(at: 0).uri var codecs = "" - // swiftlint:disable:next empty_count // swiftformat:disable:next isEmpty if !(principalModel.masterPlaylist.xStreamList.count == 0) { + // swiftlint:disable:previous empty_count if let inf = principalModel.masterPlaylist.xStreamList.xStreamInf(at: 0) { codecs = (inf.codecs as NSArray).componentsJoined(by: ",") } @@ -1816,14 +1816,14 @@ class RCTVideo: UIView, RCTVideoPlayerViewControllerDelegate, RCTPlayerObserverH guard let video = _peripheralVideo else { return nil } - return CurrentVideos.shared().video(forTag: video) + return CurrentVideos.sharedInstance.video(for: video) } func principal() -> RCTVideo? { guard let video = _principalVideo else { return nil } - return CurrentVideos.shared().video(forTag: video) + return CurrentVideos.sharedInstance.video(for: video) } func isManaged() -> Bool { diff --git a/ios/Video/WeakVideoRef.h b/ios/Video/WeakVideoRef.h deleted file mode 100644 index ea9433d50a..0000000000 --- a/ios/Video/WeakVideoRef.h +++ /dev/null @@ -1,17 +0,0 @@ -// -// WeakVideoRef.h -// react-native-video -// -// Created by marcin.dziennik on 9/8/23. -// - -#import <Foundation/Foundation.h> - -NS_ASSUME_NONNULL_BEGIN - -@class RCTVideo; -@interface WeakVideoRef : NSObject -@property(weak) RCTVideo* video; -@end - -NS_ASSUME_NONNULL_END diff --git a/ios/Video/WeakVideoRef.m b/ios/Video/WeakVideoRef.m deleted file mode 100644 index 0cb4506891..0000000000 --- a/ios/Video/WeakVideoRef.m +++ /dev/null @@ -1,12 +0,0 @@ -// -// WeakVideoRef.m -// react-native-video -// -// Created by marcin.dziennik on 9/8/23. -// - -#import "WeakVideoRef.h" - -@implementation WeakVideoRef - -@end diff --git a/ios/Video/WeakVideoRef.swift b/ios/Video/WeakVideoRef.swift new file mode 100644 index 0000000000..0ac47b48b1 --- /dev/null +++ b/ios/Video/WeakVideoRef.swift @@ -0,0 +1,7 @@ +final class WeakVideoRef { + weak var video: RCTVideo? + + init(_ video: RCTVideo) { + self.video = video + } +} From 13bfaf9753597008ae32ba50404eb350d8e9b111 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pawel=20Lozi=C5=84ski?= <pawel.lozinski@miquido.com> Date: Mon, 17 Jun 2024 20:57:40 +0200 Subject: [PATCH 32/37] Update logic for tracks info events. --- ios/RCTVideo.xcodeproj/project.pbxproj | 4 + ios/Video/Dictionary+Merge.swift | 7 ++ ios/Video/Features/RCTVideoUtils.swift | 112 ++++++++++++++++++++++++- ios/Video/PlayerModels.swift | 4 + ios/Video/RCTVideo.swift | 90 ++++---------------- 5 files changed, 138 insertions(+), 79 deletions(-) create mode 100644 ios/Video/Dictionary+Merge.swift create mode 100644 ios/Video/PlayerModels.swift diff --git a/ios/RCTVideo.xcodeproj/project.pbxproj b/ios/RCTVideo.xcodeproj/project.pbxproj index b6ebeeaef7..e36448dd98 100644 --- a/ios/RCTVideo.xcodeproj/project.pbxproj +++ b/ios/RCTVideo.xcodeproj/project.pbxproj @@ -51,6 +51,8 @@ 134814201AA4EA6300B7C361 /* libRCTVideo.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = libRCTVideo.a; sourceTree = BUILT_PRODUCTS_DIR; }; DCD62EF02C20781F00669DF5 /* WeakVideoRef.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = WeakVideoRef.swift; path = Video/WeakVideoRef.swift; sourceTree = "<group>"; }; DCD62EF12C20781F00669DF5 /* CurrentVideos.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = CurrentVideos.swift; path = Video/CurrentVideos.swift; sourceTree = "<group>"; }; + DCD62EF32C20BB4200669DF5 /* PlayerModels.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = PlayerModels.swift; path = Video/PlayerModels.swift; sourceTree = "<group>"; }; + DCD62EF52C20BBC400669DF5 /* Dictionary+Merge.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = "Dictionary+Merge.swift"; path = "Video/Dictionary+Merge.swift"; sourceTree = "<group>"; }; DCF8FF322C1AF64800CCA6B0 /* M3U8Kit */ = {isa = PBXFileReference; lastKnownFileType = folder; name = M3U8Kit; path = Video/M3U8Kit; sourceTree = "<group>"; }; /* End PBXFileReference section */ @@ -97,9 +99,11 @@ 0177D39927170A7A00F5BE18 /* RCTVideo.swift */, DCD62EF12C20781F00669DF5 /* CurrentVideos.swift */, DCD62EF02C20781F00669DF5 /* WeakVideoRef.swift */, + DCD62EF32C20BB4200669DF5 /* PlayerModels.swift */, 0177D39727170A7A00F5BE18 /* RCTVideoManager.m */, 0177D39227170A7A00F5BE18 /* RCTVideoManager.swift */, 0177D39427170A7A00F5BE18 /* RCTVideoPlayerViewController.swift */, + DCD62EF52C20BBC400669DF5 /* Dictionary+Merge.swift */, 0177D39627170A7A00F5BE18 /* RCTVideoPlayerViewControllerDelegate.swift */, 0177D39327170A7A00F5BE18 /* UIView+FindUIViewController.swift */, 0177D39827170A7A00F5BE18 /* RCTVideo-Bridging-Header.h */, diff --git a/ios/Video/Dictionary+Merge.swift b/ios/Video/Dictionary+Merge.swift new file mode 100644 index 0000000000..8db5998e3e --- /dev/null +++ b/ios/Video/Dictionary+Merge.swift @@ -0,0 +1,7 @@ +extension Dictionary { + mutating func merge(dict: [Key: Value]) { + for (k, v) in dict { + updateValue(v, forKey: k) + } + } +} diff --git a/ios/Video/Features/RCTVideoUtils.swift b/ios/Video/Features/RCTVideoUtils.swift index c13c4d9403..8ba8a916e7 100644 --- a/ios/Video/Features/RCTVideoUtils.swift +++ b/ios/Video/Features/RCTVideoUtils.swift @@ -122,7 +122,30 @@ enum RCTVideoUtils { return 0 } - static func getAudioTrackInfo(_ player: AVPlayer?) async -> [AnyObject] { + static func getModels(player: AVPlayer?, completion: @escaping (PlayerModels?) -> Void) { + guard let player, + let urlString = player.currentItem?.accessLog()?.events.last?.uri, + let url = NSURL(string: urlString), + let principalURL: NSURL = (player.currentItem?.asset as? AVURLAsset)?.url as? NSURL else { + return completion(nil) + } + + principalURL.m3u_loadAsyncCompletion { principalModel, _ in + url.m3u_loadAsyncCompletion { model, _ in + completion(.init(model: model!, principalModel: principalModel!)) + } + } + } + + static func getModels(player: AVPlayer?) async -> PlayerModels? { + await withCheckedContinuation { continuation in + getModels(player: player) { models in + continuation.resume(returning: models) + } + } + } + + static func getAudioTrackInfo(_ player: AVPlayer?, models: PlayerModels?) async -> [AnyObject] { guard let player, let asset = player.currentItem?.asset else { return [] } @@ -141,19 +164,76 @@ enum RCTVideoUtils { let language: String! = currentOption?.extendedLanguageTag ?? "" let selectedOption: AVMediaSelectionOption? = player.currentItem?.currentMediaSelection.selectedMediaOption(in: group!) - - let audioTrack = [ + var audioTrack = [ "index": NSNumber(value: i), "title": title, "language": language ?? "", "selected": currentOption?.displayName == selectedOption?.displayName, ] as [String: Any] + if let models { + let additionalTrackInfo = getAdditionalAudioTrackInfo( + player: player, + model: models.model, + principalModel: models.principalModel + ) + audioTrack.merge(dict: additionalTrackInfo) + } audioTracks.add(audioTrack) } return audioTracks as [AnyObject] } + static func getAdditionalAudioTrackInfo( + player: AVPlayer, + model _: M3U8PlaylistModel, + principalModel: M3U8PlaylistModel + ) -> [String: Any] { + var streamList: NSArray = .init() + + for i in 0 ..< principalModel.masterPlaylist.xStreamList.count { + let inf = principalModel.masterPlaylist.xStreamList.xStreamInf(at: i) + if let inf { + streamList.adding(inf) + } + } + + if let currentEvent: AVPlayerItemAccessLogEvent = player.currentItem?.accessLog()?.events.last { + let predicate = NSPredicate(format: "%K == %f", "bandwidth", currentEvent.indicatedBitrate) + let filteredArray = streamList.filtered(using: predicate) + let current = filteredArray.last + + if let current = current as? M3U8ExtXStreamInf { + let mediaList: NSArray = .init() + for i in 0 ..< principalModel.masterPlaylist.xMediaList.audio().count { + let inf = principalModel.masterPlaylist.xMediaList.audio().xMedia(at: i) + if let inf { + mediaList.adding(inf) + } + } + + let predicate = NSPredicate(format: "SELF.groupId == %@", current.audio) + + if let currentAudio = mediaList.filtered(using: predicate).last as? M3U8ExtXMedia { + let url: URL = currentAudio.m3u8URL() + let audioModel: M3U8PlaylistModel? = try? .init(url: url) + + if let audioModel { + let audioInfo: M3U8SegmentInfo = audioModel.mainMediaPl.segmentList.segmentInfo(at: 0) + + return [ + "codecs": currentAudio.groupId(), + "file": audioInfo.uri.absoluteString, + "channels": currentAudio.channels, + ] + } + } + } + } + + return .init() + } + static func getTextTrackInfo(_ player: AVPlayer?) async -> [TextTrack] { guard let player, let asset = player.currentItem?.asset else { return [] @@ -185,6 +265,32 @@ enum RCTVideoUtils { return textTracks } + static func getVideoTrackInfo(models: PlayerModels?) -> [AnyObject] { + guard let models else { return [] } + let videoTracks: NSMutableArray! = NSMutableArray() + // swiftformat:disable:next isEmpty + if !(models.model.mainMediaPl.segmentList.count == 0) { // swiftlint:disable:this empty_count + let uri: URL = models.model.mainMediaPl.segmentList.segmentInfo(at: 0).uri + + var codecs = "" + // swiftformat:disable:next isEmpty + if !(models.principalModel.masterPlaylist.xStreamList.count == 0) { // swiftlint:disable:this empty_count + if let inf = models.principalModel.masterPlaylist.xStreamList.xStreamInf(at: 0) { + codecs = (inf.codecs as NSArray).componentsJoined(by: ",") + } + } + + let stringURL = uri.absoluteString as NSString + videoTracks.add( + [ + "file": stringURL, + "codecs": codecs, + ] + ) + } + return .init() + } + // UNUSED static func getCurrentTime(playerItem: AVPlayerItem?) -> Float { return Float(CMTimeGetSeconds(playerItem?.currentTime() ?? .zero)) diff --git a/ios/Video/PlayerModels.swift b/ios/Video/PlayerModels.swift new file mode 100644 index 0000000000..ae81e76b1b --- /dev/null +++ b/ios/Video/PlayerModels.swift @@ -0,0 +1,4 @@ +struct PlayerModels { + let model: M3U8PlaylistModel + let principalModel: M3U8PlaylistModel +} diff --git a/ios/Video/RCTVideo.swift b/ios/Video/RCTVideo.swift index 419265a7b1..fc3260464f 100644 --- a/ios/Video/RCTVideo.swift +++ b/ios/Video/RCTVideo.swift @@ -682,33 +682,6 @@ class RCTVideo: UIView, RCTVideoPlayerViewControllerDelegate, RCTPlayerObserverH return .init() } - func getVideoTrackInfo( - model: M3U8PlaylistModel, - principalModel: M3U8PlaylistModel - ) -> [String: Any] { - // swiftformat:disable:next isEmpty - if !(model.mainMediaPl.segmentList.count == 0) { - // swiftlint:disable:previous empty_count - let uri: URL = model.mainMediaPl.segmentList.segmentInfo(at: 0).uri - - var codecs = "" - // swiftformat:disable:next isEmpty - if !(principalModel.masterPlaylist.xStreamList.count == 0) { - // swiftlint:disable:previous empty_count - if let inf = principalModel.masterPlaylist.xStreamList.xStreamInf(at: 0) { - codecs = (inf.codecs as NSArray).componentsJoined(by: ",") - } - } - - let stringURL = uri.absoluteString as NSString - return [ - "file": stringURL, - "codecs": codecs, - ] - } - return .init() - } - // MARK: - Prop setters @objc @@ -1503,9 +1476,10 @@ class RCTVideo: UIView, RCTVideoPlayerViewControllerDelegate, RCTPlayerObserverH height = Float(naturalSize.height) } orientation = width > height ? "landscape" : width == height ? "square" : "portrait" - - let audioTracks = await RCTVideoUtils.getAudioTrackInfo(self._player) + let models = await RCTVideoUtils.getModels(player: _player) + let audioTracks = await RCTVideoUtils.getAudioTrackInfo(self._player, models: models) let textTracks = await RCTVideoUtils.getTextTrackInfo(self._player) + let videoTracks = RCTVideoUtils.getVideoTrackInfo(models: models) self.onVideoLoad?(["duration": NSNumber(value: duration), "currentTime": NSNumber(value: Float(CMTimeGetSeconds(_playerItem.currentTime()))), "canPlayReverse": NSNumber(value: _playerItem.canPlayReverse), @@ -1521,61 +1495,16 @@ class RCTVideo: UIView, RCTVideoPlayerViewControllerDelegate, RCTPlayerObserverH ], "audioTracks": audioTracks, "textTracks": self._textTracks?.compactMap { $0.json } ?? textTracks.map(\.json), + "videoTracks": videoTracks, "target": self.reactTag as Any]) } self._videoLoadStarted = false - self.handleMetadataUpdateForTrackChange() self._playerObserver.attachPlayerEventListeners() self.applyModifiers() } } - func handleMetadataUpdateForTrackChange() { - if onTextTracks != nil { - self.onTextTracks?( - [ - "textTrack": self._textTracks, - ] - ) - } - - guard let player = _player, - let urlString = _player?.currentItem?.accessLog()?.events.last?.uri, - let url = NSURL(string: urlString), - let principalURL: NSURL = (_player?.currentItem?.asset as? AVURLAsset)?.url as? NSURL else { - return - } - - principalURL.m3u_loadAsyncCompletion { principalModel, _ in - url.m3u_loadAsyncCompletion { model, _ in - if let model, let principalModel { - if self.onAudioTracks != nil { - self.onAudioTracks?( - [ - "audioTrack": self.getAudioTrackInfo( - model: model, - principalModel: principalModel - ), - ] - ) - } - - if self.onVideoTracks != nil { - self.onVideoTracks?( - [ - "videoTrack": self.getVideoTrackInfo( - model: model, - principalModel: principalModel - ), - ] - ) - } - } - } - } - } - func handlePlaybackFailed() { if let player = _player { NowPlayingInfoCenterManager.shared.removePlayer(player: player) @@ -1778,10 +1707,19 @@ class RCTVideo: UIView, RCTVideoPlayerViewControllerDelegate, RCTPlayerObserverH if onAudioTracks != nil { Task { - let audioTracks = await RCTVideoUtils.getAudioTrackInfo(self._player) + let models = await RCTVideoUtils.getModels(player: _player) + let audioTracks = await RCTVideoUtils.getAudioTrackInfo(self._player, models: models!) self.onAudioTracks?(["audioTracks": audioTracks]) } } + + if onVideoTracks != nil { + Task { + let models = await RCTVideoUtils.getModels(player: _player) + let videoTracks = await RCTVideoUtils.getVideoTrackInfo(models: models) + self.onAudioTracks?(["videoTracks": videoTracks]) + } + } } func handleLegibleOutput(strings: [NSAttributedString]) { From 0f621d0b11c3ac7266b6e8138019dbbbfddad1a1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pawel=20Lozi=C5=84ski?= <pawel.lozinski@miquido.com> Date: Tue, 18 Jun 2024 10:42:07 +0200 Subject: [PATCH 33/37] Post review changes. --- ios/Video/RCTVideo.swift | 66 ++++------------------------------------ 1 file changed, 6 insertions(+), 60 deletions(-) diff --git a/ios/Video/RCTVideo.swift b/ios/Video/RCTVideo.swift index fc3260464f..4ba83a612d 100644 --- a/ios/Video/RCTVideo.swift +++ b/ios/Video/RCTVideo.swift @@ -630,58 +630,6 @@ class RCTVideo: UIView, RCTVideoPlayerViewControllerDelegate, RCTPlayerObserverH return playerItem } - func getAudioTrackInfo( - model _: M3U8PlaylistModel, - principalModel: M3U8PlaylistModel - ) -> [String: Any] { - var streamList: NSArray = .init() - - for i in 0 ..< principalModel.masterPlaylist.xStreamList.count { - let inf = principalModel.masterPlaylist.xStreamList.xStreamInf(at: i) - if let inf { - streamList.adding(inf) - } - } - - if let currentEvent: AVPlayerItemAccessLogEvent = _player?.currentItem?.accessLog()?.events.last { - let predicate = NSPredicate(format: "%K == %f", "bandwidth", currentEvent.indicatedBitrate) - let filteredArray = streamList.filtered(using: predicate) - let current = filteredArray.last - - if let current = current as? M3U8ExtXStreamInf { - let mediaList: NSArray = .init() - for i in 0 ..< principalModel.masterPlaylist.xMediaList.audio().count { - let inf = principalModel.masterPlaylist.xMediaList.audio().xMedia(at: i) - if let inf { - mediaList.adding(inf) - } - } - - let predicate = NSPredicate(format: "SELF.groupId == %@", current.audio) - let currentAudio = mediaList.filtered(using: predicate).last - - if let currentAudio = currentAudio as? M3U8ExtXMedia { - let url: URL = currentAudio.m3u8URL() - let audioModel: M3U8PlaylistModel? = try? .init(url: url) - - if let audioModel { - let audioInfo: M3U8SegmentInfo = audioModel.mainMediaPl.segmentList.segmentInfo(at: 0) - - return [ - "title": currentAudio.name(), - "language": currentAudio.language(), - "codecs": currentAudio.groupId(), - "file": audioInfo.uri.absoluteString, - "channels": currentAudio.channels, - ] - } - } - } - } - - return .init() - } - // MARK: - Prop setters @objc @@ -1705,19 +1653,17 @@ class RCTVideo: UIView, RCTVideoPlayerViewControllerDelegate, RCTPlayerObserverH } } - if onAudioTracks != nil { - Task { - let models = await RCTVideoUtils.getModels(player: _player) + Task { + let models = await RCTVideoUtils.getModels(player: _player) + + if onAudioTracks != nil { let audioTracks = await RCTVideoUtils.getAudioTrackInfo(self._player, models: models!) self.onAudioTracks?(["audioTracks": audioTracks]) } - } - if onVideoTracks != nil { - Task { - let models = await RCTVideoUtils.getModels(player: _player) + if onVideoTracks != nil { let videoTracks = await RCTVideoUtils.getVideoTrackInfo(models: models) - self.onAudioTracks?(["videoTracks": videoTracks]) + self.onVideoTracks?(["videoTracks": videoTracks]) } } } From 53110205d29ba3701b378796b789ae79f81346cf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pawel=20Lozi=C5=84ski?= <pawel.lozinski@miquido.com> Date: Tue, 18 Jun 2024 11:40:07 +0200 Subject: [PATCH 34/37] Update docs. --- CHANGELOG.md | 3 ++- docs/pages/component/events.mdx | 16 ++++++++-------- 2 files changed, 10 insertions(+), 9 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index fec5373bfb..54e3dfa741 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,8 +6,9 @@ * **ios:** XHUB-5327 - Expose channel (HLS) and Dolby SupplementalProperty (DASH) values from currently played resource to ReactNative ([675207f](https://github.com/miquido/react-native-video/commit/675207f6dbe7b817bb2c19292a9c487406b36ea1) ([a6823bb])(https://github.com/miquido/react-native-video/commit/a6823bbca0cfa135b995bd2117f18b6834ba20ad) -* **ios:** Send on played tracks change +* **ios:** Add onVideoTracks event. ([1b96ed6])(https://github.com/miquido/react-native-video/pull/25/commits/1b96ed6025d65484ddab7391366c8c97ea788938) + ([13bfaf9])(https://github.com/miquido/react-native-video/pull/25/commits/13bfaf9753597008ae32ba50404eb350d8e9b111) * **ios:** Attempt to sync two videos. ([300794d])(https://github.com/miquido/react-native-video/commit/300794dd73d36c6722106ad6ec3399a580d3c407) diff --git a/docs/pages/component/events.mdx b/docs/pages/component/events.mdx index fd4507a125..8c4b3379a6 100644 --- a/docs/pages/component/events.mdx +++ b/docs/pages/component/events.mdx @@ -555,15 +555,15 @@ Payload: | Property | Type | Description | | -------- | ------- | ------------| -| index | number | Track index | -| trackId | string | Track ID of the video track | +| index | number | Track index (android only) | +| trackId | string | Track ID of the video track (android only) | | codecs | string | MimeType of codec used for this track | -| width | number | Track width | -| height | number | Track height | -| bitrate | number | Bitrate in bps | -| selected | boolean | true if track is selected for playing | -| file | string | File name from DASH manifest | -| supplementalProperties | string | Supplemental properties for the adaptation set from DASH manifest | +| width | number | Track width (android only) | +| height | number | Track height (android only) | +| bitrate | number | Bitrate in bps (android only) | +| selected | boolean | true if track is selected for playing (android only) | +| file | string | File name from DASH manifest on Android nad M3U8 file on iOS | +| supplementalProperties | string | Supplemental properties for the adaptation set from DASH manifest (android only) | Example: From 484a58d1824c11c43ea492f8cfba5a11d8971144 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pawel=20Lozi=C5=84ski?= <pawel.lozinski@miquido.com> Date: Tue, 18 Jun 2024 11:55:04 +0200 Subject: [PATCH 35/37] Update event on audio track --- docs/pages/component/events.mdx | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/docs/pages/component/events.mdx b/docs/pages/component/events.mdx index 8c4b3379a6..b2e67c460c 100644 --- a/docs/pages/component/events.mdx +++ b/docs/pages/component/events.mdx @@ -52,11 +52,13 @@ An **array** of | trackId | string | Track ID of the video track (android only) | | title | string | Descriptive name for the track | | language | string | 2 letter [ISO 639-1 code](https://en.wikipedia.org/wiki/List_of_ISO_639-1_codes) representing the language | -| bitrate | number | bitrate of track | -| type | string | Mime type of track | +| bitrate | number | bitrate of track (android only) | +| type | string | Mime type of track (android only) | | selected | boolean | true if track is playing | -| file | string | File name from DASH manifest (android only) | +| file | string | File name from DASH manifest on Android and M3U8 file on iOS | | supplementalProperties | string | Supplemental properties for the adaptation set from DASH manifest (android only) | +| codecs | string | MimeType of codec used for this track | +| channels | array | Relevant to ports of hardware channels | Example: @@ -562,7 +564,7 @@ Payload: | height | number | Track height (android only) | | bitrate | number | Bitrate in bps (android only) | | selected | boolean | true if track is selected for playing (android only) | -| file | string | File name from DASH manifest on Android nad M3U8 file on iOS | +| file | string | File name from DASH manifest on Android and M3U8 file on iOS | | supplementalProperties | string | Supplemental properties for the adaptation set from DASH manifest (android only) | Example: From 05f7932496e84eb14775a21922aac444d348f04b Mon Sep 17 00:00:00 2001 From: Roman Savka <roman.savka@miquido.com> Date: Tue, 18 Jun 2024 12:10:37 +0200 Subject: [PATCH 36/37] Update video/audio tracks structures in JS --- docs/pages/component/events.mdx | 2 +- src/specs/VideoNativeComponent.ts | 8 ++++++++ src/types/events.ts | 2 ++ 3 files changed, 11 insertions(+), 1 deletion(-) diff --git a/docs/pages/component/events.mdx b/docs/pages/component/events.mdx index b2e67c460c..47e23272e7 100644 --- a/docs/pages/component/events.mdx +++ b/docs/pages/component/events.mdx @@ -58,7 +58,7 @@ An **array** of | file | string | File name from DASH manifest on Android and M3U8 file on iOS | | supplementalProperties | string | Supplemental properties for the adaptation set from DASH manifest (android only) | | codecs | string | MimeType of codec used for this track | -| channels | array | Relevant to ports of hardware channels | +| channels | array | Relevant to ports of hardware channels (iOS only) | Example: diff --git a/src/specs/VideoNativeComponent.ts b/src/specs/VideoNativeComponent.ts index 64a34536d2..0b701de5b5 100644 --- a/src/specs/VideoNativeComponent.ts +++ b/src/specs/VideoNativeComponent.ts @@ -141,9 +141,13 @@ type OnLoadData = Readonly<{ trackId: string; title?: string; language?: string; + codecs?: string; bitrate?: Float; type?: string; selected?: boolean; + file?: string; + supplementalProperties?: string; + channels?: string; }[]; textTracks: { index: Int32; @@ -165,6 +169,8 @@ type OnLoadData = Readonly<{ height?: Float; bitrate?: Float; selected?: boolean; + file?: string; + supplementalProperties?: string; }[]; }>; @@ -216,11 +222,13 @@ type OnAudioTracksData = Readonly<{ trackId: string; title?: string; language?: string; + codecs?: string; bitrate?: Float; type?: string; selected?: boolean; file?: string; supplementalProperties?: string; + channels?: string; }[]; }>; diff --git a/src/types/events.ts b/src/types/events.ts index 07d203c0de..e43fce01c5 100644 --- a/src/types/events.ts +++ b/src/types/events.ts @@ -81,11 +81,13 @@ export type OnAudioTracksData = Readonly<{ trackId: string; title?: string; language?: string; + codecs?: string; bitrate?: number; type?: string; selected?: boolean; file?: string; supplementalProperties?: string; + channels?: string; }[]; }>; From ddedec8755179d1bf487d9da40873ea0c707de33 Mon Sep 17 00:00:00 2001 From: Roman Savka <roman.savka@miquido.com> Date: Tue, 18 Jun 2024 12:15:02 +0200 Subject: [PATCH 37/37] Expose codecs on audio tracks from android --- .../com/brentvatne/common/react/VideoEventEmitter.java | 9 ++++++--- .../com/brentvatne/exoplayer/ReactExoplayerView.java | 1 + 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/android/src/main/java/com/brentvatne/common/react/VideoEventEmitter.java b/android/src/main/java/com/brentvatne/common/react/VideoEventEmitter.java index 491600c618..8ad95d0907 100644 --- a/android/src/main/java/com/brentvatne/common/react/VideoEventEmitter.java +++ b/android/src/main/java/com/brentvatne/common/react/VideoEventEmitter.java @@ -199,7 +199,9 @@ public void load( int videoWidth, int videoHeight, List<TrackInfo> audioTracks, - List<TrackInfo> videoTracks, List<TrackInfo> textTracks, + List<TrackInfo> videoTracks, + List<TrackInfo> textTracks, + @Nullable Object manifest, String trackId) { WritableMap event = Arguments.createMap(); event.putDouble(EVENT_PROP_DURATION, duration / 1000D); @@ -210,12 +212,12 @@ public void load( event.putString(EVENT_PROP_TRACK_ID, trackId); WritableArray videoTrackArray = Arguments.createArray(); for (TrackInfo track : videoTracks) { - videoTrackArray.pushMap(createVideoTrackInfo(track, false, null)); + videoTrackArray.pushMap(createVideoTrackInfo(track, false, manifest)); } event.putArray(EVENT_PROP_VIDEO_TRACKS, videoTrackArray); WritableArray audioTrackArray = Arguments.createArray(); for (TrackInfo track : audioTracks) { - audioTrackArray.pushMap(createAudioTrackInfo(track, false, null)); + audioTrackArray.pushMap(createAudioTrackInfo(track, false, manifest)); } event.putArray(EVENT_PROP_AUDIO_TRACKS, audioTrackArray); WritableArray textTrackArray = Arguments.createArray(); @@ -457,6 +459,7 @@ private static WritableMap createAudioTrackInfo(TrackInfo track, boolean selecte audioTrack.putString("type", format.sampleMimeType); audioTrack.putString("language", format.language); audioTrack.putInt("bitrate", format.bitrate == Format.NO_VALUE ? 0 : format.bitrate); + audioTrack.putString("codecs", format.codecs); audioTrack.putBoolean("selected", selected); if (manifest != null) { Representation representation = ManifestUtils.getRepresentationOf(manifest, track); diff --git a/android/src/main/java/com/brentvatne/exoplayer/ReactExoplayerView.java b/android/src/main/java/com/brentvatne/exoplayer/ReactExoplayerView.java index 02a50e0161..d4aabcdc34 100644 --- a/android/src/main/java/com/brentvatne/exoplayer/ReactExoplayerView.java +++ b/android/src/main/java/com/brentvatne/exoplayer/ReactExoplayerView.java @@ -1338,6 +1338,7 @@ private void videoLoaded() { TracksUtil.getAudioTracks(info), TracksUtil.getVideoTracks(info), TracksUtil.getTextTracks(info), + player.getCurrentManifest(), trackId ); }