chore: add custom titles, description and chapters

This commit is contained in:
Konstantin Späth 2023-08-13 00:01:27 +02:00
parent fd2e396262
commit b225b0f800
6 changed files with 142 additions and 3 deletions

View File

@ -343,7 +343,11 @@ export default class Video extends Component {
patchVer: source.patchVer || 0, patchVer: source.patchVer || 0,
requestHeaders: source.headers ? this.stringsOnlyObject(source.headers) : {}, requestHeaders: source.headers ? this.stringsOnlyObject(source.headers) : {},
startTime: source.startTime || 0, startTime: source.startTime || 0,
endTime: source.endTime endTime: source.endTime,
// Custom Metadata
title: source.title,
subtitle: source.subtitle,
description: source.description,
}, },
onVideoLoadStart: this._onLoadStart, onVideoLoadStart: this._onLoadStart,
onVideoPlaybackStateChanged: this._onPlaybackStateChanged, onVideoPlaybackStateChanged: this._onPlaybackStateChanged,
@ -492,6 +496,13 @@ Video.propTypes = {
language: PropTypes.string.isRequired, language: PropTypes.string.isRequired,
}) })
), ),
chapters: PropTypes.arrayOf(
PropTypes.shape({
title: PropTypes.string,
startTime: PropTypes.number.isRequired,
endTime: PropTypes.number.isRequired,
})
),
paused: PropTypes.bool, paused: PropTypes.bool,
muted: PropTypes.bool, muted: PropTypes.bool,
volume: PropTypes.number, volume: PropTypes.number,

View File

@ -13,11 +13,26 @@ export default function App() {
uri: 'https://commondatastorage.googleapis.com/gtv-videos-bucket/sample/BigBuckBunny.mp4', uri: 'https://commondatastorage.googleapis.com/gtv-videos-bucket/sample/BigBuckBunny.mp4',
// uri: 'https://sample.vodobox.net/skate_phantom_flex_4k/skate_phantom_flex_4k.m3u8', // uri: 'https://sample.vodobox.net/skate_phantom_flex_4k/skate_phantom_flex_4k.m3u8',
// type: 'm3u8', // type: 'm3u8',
title: 'Custom Title',
subtitle: 'Custom Subtitle',
description: 'Custom Description',
}} }}
style={[styles.fullScreen, StyleSheet.absoluteFillObject]} style={[styles.fullScreen, StyleSheet.absoluteFillObject]}
controls controls
fullscreen fullscreen
resizeMode={'contain'} resizeMode={'contain'}
chapters={[
{
title: 'Chapter 1',
startTime: 0.0,
endTime: 20.0,
},
{
title: 'Chapter 2',
startTime: 20.0,
endTime: 40.0,
},
]}
/> />
</> </>
); );

View File

@ -0,0 +1,25 @@
struct Chapter {
let title: String
let uri: String
let startTime: Double
let endTime: Double
let json: NSDictionary?
init(_ json: NSDictionary!) {
guard json != nil else {
self.json = nil
self.title = ""
self.uri = ""
self.startTime = 0
self.endTime = 0
return
}
self.json = json
self.title = json["title"] as? String ?? ""
self.uri = json["uri"] as? String ?? ""
self.startTime = json["startTime"] as? Double ?? 0
self.endTime = json["endTime"] as? Double ?? 0
}
}

View File

@ -8,6 +8,10 @@ struct VideoSource {
let requestHeaders: Dictionary<String,Any>? let requestHeaders: Dictionary<String,Any>?
let startTime: Int64? let startTime: Int64?
let endTime: Int64? let endTime: Int64?
// Custom Metadata
let title: String?
let subtitle: String?
let description: String?
let json: NSDictionary? let json: NSDictionary?
@ -22,6 +26,9 @@ struct VideoSource {
self.requestHeaders = nil self.requestHeaders = nil
self.startTime = nil self.startTime = nil
self.endTime = nil self.endTime = nil
self.title = nil
self.subtitle = nil
self.description = nil
return return
} }
self.json = json self.json = json
@ -33,5 +40,8 @@ struct VideoSource {
self.requestHeaders = json["requestHeaders"] as? Dictionary<String,Any> self.requestHeaders = json["requestHeaders"] as? Dictionary<String,Any>
self.startTime = json["startTime"] as? Int64 self.startTime = json["startTime"] as? Int64
self.endTime = json["endTime"] as? Int64 self.endTime = json["endTime"] as? Int64
self.title = json["title"] as? String
self.subtitle = json["subtitle"] as? String
self.description = json["description"] as? String
} }
} }

View File

@ -14,6 +14,7 @@ class RCTVideo: UIView, RCTVideoPlayerViewControllerDelegate, RCTPlayerObserverH
private var _source:VideoSource? private var _source:VideoSource?
private var _playerBufferEmpty:Bool = true private var _playerBufferEmpty:Bool = true
private var _playerLayer:AVPlayerLayer? private var _playerLayer:AVPlayerLayer?
private var _chapters:[Chapter]?
private var _playerViewController:RCTVideoPlayerViewController? private var _playerViewController:RCTVideoPlayerViewController?
private var _videoURL:NSURL? private var _videoURL:NSURL?
@ -356,7 +357,7 @@ class RCTVideo: UIView, RCTVideoPlayerViewControllerDelegate, RCTPlayerObserverH
func playerItemPrepareText(asset:AVAsset!, assetOptions:NSDictionary?) -> AVPlayerItem { func playerItemPrepareText(asset:AVAsset!, assetOptions:NSDictionary?) -> AVPlayerItem {
if (_textTracks == nil) || _textTracks?.count==0 { if (_textTracks == nil) || _textTracks?.count==0 {
return AVPlayerItem(asset: asset) return self.playerItemPropegateMetadata(AVPlayerItem(asset: asset))
} }
// AVPlayer can't airplay AVMutableCompositions // AVPlayer can't airplay AVMutableCompositions
@ -371,7 +372,74 @@ class RCTVideo: UIView, RCTVideoPlayerViewControllerDelegate, RCTPlayerObserverH
setTextTracks(validTextTracks) setTextTracks(validTextTracks)
} }
return AVPlayerItem(asset: mixComposition) return self.playerItemPropegateMetadata(AVPlayerItem(asset: mixComposition))
}
func playerItemPropegateMetadata(_ playerItem: AVPlayerItem!) -> AVPlayerItem {
var mapping: [AVMetadataIdentifier: Any] = [:]
if let title = _source?.title {
mapping[.commonIdentifierTitle] = title
}
if let subtitle = _source?.subtitle {
mapping[.iTunesMetadataTrackSubTitle] = subtitle
}
if let description = _source?.description {
mapping[.commonIdentifierDescription] = description
}
if !mapping.isEmpty {
playerItem.externalMetadata = createMetadataItems(for: mapping)
}
if let chapters = _chapters {
playerItem.navigationMarkerGroups = makeNavigationMarkerGroups(chapters)
}
return playerItem
}
func createMetadataItems(for mapping: [AVMetadataIdentifier: Any]) -> [AVMetadataItem] {
return mapping.compactMap { createMetadataItem(for:$0, value:$1) }
}
private func makeNavigationMarkerGroups(_ chapters: [Chapter]) -> [AVNavigationMarkersGroup] {
var metadataGroups = [AVTimedMetadataGroup]()
// Iterate over the defined chapters and build a timed metadata group object for each.
chapters.forEach { chapter in
metadataGroups.append(makeTimedMetadataGroup(for: chapter))
}
return [AVNavigationMarkersGroup(title: nil, timedNavigationMarkers: metadataGroups)]
}
private func makeTimedMetadataGroup(for chapter: Chapter) -> AVTimedMetadataGroup {
var metadata = [AVMetadataItem]()
// Create a metadata item that contains the chapter title.
let titleItem = createMetadataItem(for: .commonIdentifierTitle, value: chapter.title)
metadata.append(titleItem)
// Create a time range for the metadata group.
let timescale: Int32 = 600
let startTime = CMTime(seconds: chapter.startTime, preferredTimescale: timescale)
let endTime = CMTime(seconds: chapter.endTime, preferredTimescale: timescale)
let timeRange = CMTimeRangeFromTimeToTime(start: startTime, end: endTime)
return AVTimedMetadataGroup(items: metadata, timeRange: timeRange)
}
private func createMetadataItem(for identifier: AVMetadataIdentifier,
value: Any) -> AVMetadataItem {
let item = AVMutableMetadataItem()
item.identifier = identifier
item.value = value as? NSCopying & NSObjectProtocol
// Specify "und" to indicate an undefined language.
item.extendedLanguageTag = "und"
return item.copy() as! AVMetadataItem
} }
// MARK: - Prop setters // MARK: - Prop setters
@ -640,6 +708,15 @@ class RCTVideo: UIView, RCTVideoPlayerViewControllerDelegate, RCTPlayerObserverH
// in case textTracks was set after selectedTextTrack // in case textTracks was set after selectedTextTrack
if (_selectedTextTrackCriteria != nil) {setSelectedTextTrack(_selectedTextTrackCriteria)} if (_selectedTextTrackCriteria != nil) {setSelectedTextTrack(_selectedTextTrackCriteria)}
} }
@objc
func setChapters(_ chapters:[NSDictionary]?) {
setChapters(chapters?.map { Chapter($0) })
}
func setChapters(_ chapters:[Chapter]?) {
_chapters = chapters
}
@objc @objc
func setFullscreen(_ fullscreen:Bool) { func setFullscreen(_ fullscreen:Bool) {

View File

@ -14,6 +14,7 @@ RCT_EXPORT_VIEW_PROPERTY(allowsExternalPlayback, BOOL);
RCT_EXPORT_VIEW_PROPERTY(textTracks, NSArray); RCT_EXPORT_VIEW_PROPERTY(textTracks, NSArray);
RCT_EXPORT_VIEW_PROPERTY(selectedTextTrack, NSDictionary); RCT_EXPORT_VIEW_PROPERTY(selectedTextTrack, NSDictionary);
RCT_EXPORT_VIEW_PROPERTY(selectedAudioTrack, NSDictionary); RCT_EXPORT_VIEW_PROPERTY(selectedAudioTrack, NSDictionary);
RCT_EXPORT_VIEW_PROPERTY(chapters, NSArray);
RCT_EXPORT_VIEW_PROPERTY(paused, BOOL); RCT_EXPORT_VIEW_PROPERTY(paused, BOOL);
RCT_EXPORT_VIEW_PROPERTY(muted, BOOL); RCT_EXPORT_VIEW_PROPERTY(muted, BOOL);
RCT_EXPORT_VIEW_PROPERTY(controls, BOOL); RCT_EXPORT_VIEW_PROPERTY(controls, BOOL);