chore: add custom titles, description and chapters
This commit is contained in:
parent
fd2e396262
commit
b225b0f800
13
Video.js
13
Video.js
@ -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,
|
||||||
|
@ -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,
|
||||||
|
},
|
||||||
|
]}
|
||||||
/>
|
/>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
|
25
ios/Video/DataStructures/Chapter.swift
Normal file
25
ios/Video/DataStructures/Chapter.swift
Normal 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
|
||||||
|
}
|
||||||
|
}
|
@ -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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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
|
||||||
@ -641,6 +709,15 @@ class RCTVideo: UIView, RCTVideoPlayerViewControllerDelegate, RCTPlayerObserverH
|
|||||||
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) {
|
||||||
if fullscreen && !_fullscreenPlayerPresented && _player != nil {
|
if fullscreen && !_fullscreenPlayerPresented && _player != nil {
|
||||||
|
@ -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);
|
||||||
|
Loading…
Reference in New Issue
Block a user