From 2648502b364c2802f5a2a7302c31200905c0a807 Mon Sep 17 00:00:00 2001 From: YangJH Date: Fri, 24 Nov 2023 20:52:46 +0900 Subject: [PATCH] feat: implement startPosition (#3355) * feat(android): implement startPosition * feat(ios): implement startPosition * feat: implement startPosition type * docs: fix typo * docs: update startPosition * refactor: put startPosition inside source prop - put startPosition inside source prop - rename existing prop (startTime, endTime) * docs: update startPosition property description * fix: fix invalid assignments * refactor: remove redundant optional chaining * feat: allow "0" to work too --- .../exoplayer/ReactExoplayerView.java | 42 +++++++++++-------- .../exoplayer/ReactExoplayerViewManager.java | 13 +++--- docs/pages/component/props.md | 18 +++++--- ios/Video/DataStructures/VideoSource.swift | 15 ++++--- ios/Video/Features/RCTVideoUtils.swift | 8 ++-- ios/Video/RCTVideo.swift | 20 +++++++-- src/Video.tsx | 7 ++-- src/VideoNativeComponent.ts | 5 ++- src/types/video.ts | 5 ++- 9 files changed, 86 insertions(+), 47 deletions(-) diff --git a/android/src/main/java/com/brentvatne/exoplayer/ReactExoplayerView.java b/android/src/main/java/com/brentvatne/exoplayer/ReactExoplayerView.java index ab11643c..7cd35d6d 100644 --- a/android/src/main/java/com/brentvatne/exoplayer/ReactExoplayerView.java +++ b/android/src/main/java/com/brentvatne/exoplayer/ReactExoplayerView.java @@ -192,8 +192,9 @@ public class ReactExoplayerView extends FrameLayout implements // Props from React private int backBufferDurationMs = DefaultLoadControl.DEFAULT_BACK_BUFFER_DURATION_MS; private Uri srcUri; - private long startTimeMs = -1; - private long endTimeMs = -1; + private long startPositionMs = -1; + private long cropStartMs = -1; + private long cropEndMs = -1; private String extension; private boolean repeat; private String audioTrackType; @@ -662,7 +663,7 @@ public class ReactExoplayerView extends FrameLayout implements private void initializePlayerSource(ReactExoplayerView self, DrmSessionManager drmSessionManager) { ArrayList mediaSourceList = buildTextSources(); - MediaSource videoSource = buildMediaSource(self.srcUri, self.extension, drmSessionManager, startTimeMs, endTimeMs); + MediaSource videoSource = buildMediaSource(self.srcUri, self.extension, drmSessionManager, cropStartMs, cropEndMs); MediaSource mediaSourceWithAds = null; if (adTagUrl != null) { MediaSource.Factory mediaSourceFactory = new DefaultMediaSourceFactory(mediaDataSourceFactory) @@ -703,7 +704,12 @@ public class ReactExoplayerView extends FrameLayout implements if (haveResumePosition) { player.seekTo(resumeWindow, resumePosition); } - player.prepare(mediaSource, !haveResumePosition, false); + if (startPositionMs >= 0) { + player.setMediaSource(mediaSource, startPositionMs); + } else { + player.setMediaSource(mediaSource, !haveResumePosition); + } + player.prepare(); playerNeedsSource = false; reLayout(exoPlayerView); @@ -761,7 +767,7 @@ public class ReactExoplayerView extends FrameLayout implements } } - private MediaSource buildMediaSource(Uri uri, String overrideExtension, DrmSessionManager drmSessionManager, long startTimeMs, long endTimeMs) { + private MediaSource buildMediaSource(Uri uri, String overrideExtension, DrmSessionManager drmSessionManager, long cropStartMs, long cropEndMs) { if (uri == null) { throw new IllegalStateException("Invalid video uri"); } @@ -822,12 +828,12 @@ public class ReactExoplayerView extends FrameLayout implements ) .createMediaSource(mediaItem); - if (startTimeMs >= 0 && endTimeMs >= 0) { - return new ClippingMediaSource(mediaSource, startTimeMs * 1000, endTimeMs * 1000); - } else if (startTimeMs >= 0) { - return new ClippingMediaSource(mediaSource, startTimeMs * 1000, TIME_END_OF_SOURCE); - } else if (endTimeMs >= 0) { - return new ClippingMediaSource(mediaSource, 0, endTimeMs * 1000); + if (cropStartMs >= 0 && cropEndMs >= 0) { + return new ClippingMediaSource(mediaSource, cropStartMs * 1000, cropEndMs * 1000); + } else if (cropStartMs >= 0) { + return new ClippingMediaSource(mediaSource, cropStartMs * 1000, TIME_END_OF_SOURCE); + } else if (cropEndMs >= 0) { + return new ClippingMediaSource(mediaSource, 0, cropEndMs * 1000); } return mediaSource; @@ -1500,13 +1506,14 @@ public class ReactExoplayerView extends FrameLayout implements // ReactExoplayerViewManager public api - public void setSrc(final Uri uri, final long startTimeMs, final long endTimeMs, final String extension, Map headers) { + public void setSrc(final Uri uri, final long startPositionMs, final long cropStartMs, final long cropEndMs, final String extension, Map headers) { if (uri != null) { - boolean isSourceEqual = uri.equals(srcUri) && startTimeMs == this.startTimeMs && endTimeMs == this.endTimeMs; + boolean isSourceEqual = uri.equals(srcUri) && cropStartMs == this.cropStartMs && cropEndMs == this.cropEndMs; hasDrmFailed = false; this.srcUri = uri; - this.startTimeMs = startTimeMs; - this.endTimeMs = endTimeMs; + this.startPositionMs = startPositionMs; + this.cropStartMs = cropStartMs; + this.cropEndMs = cropEndMs; this.extension = extension; this.requestHeaders = headers; this.mediaDataSourceFactory = @@ -1524,8 +1531,9 @@ public class ReactExoplayerView extends FrameLayout implements player.stop(); player.clearMediaItems(); this.srcUri = null; - this.startTimeMs = -1; - this.endTimeMs = -1; + this.startPositionMs = -1; + this.cropStartMs = -1; + this.cropEndMs = -1; this.extension = null; this.requestHeaders = null; this.mediaDataSourceFactory = null; diff --git a/android/src/main/java/com/brentvatne/exoplayer/ReactExoplayerViewManager.java b/android/src/main/java/com/brentvatne/exoplayer/ReactExoplayerViewManager.java index 7da5c19c..7cf6cbb2 100644 --- a/android/src/main/java/com/brentvatne/exoplayer/ReactExoplayerViewManager.java +++ b/android/src/main/java/com/brentvatne/exoplayer/ReactExoplayerViewManager.java @@ -36,8 +36,9 @@ public class ReactExoplayerViewManager extends ViewGroupManager headers = src.hasKey(PROP_SRC_HEADERS) ? ReactBridgeUtils.toStringMap(src.getMap(PROP_SRC_HEADERS)) : new HashMap<>(); @@ -166,7 +169,7 @@ public class ReactExoplayerViewManager extends ViewGroupManager? - let startTime: Int64? - let endTime: Int64? + let startPosition: Int64? + let cropStart: Int64? + let cropEnd: Int64? // Custom Metadata let title: String? let subtitle: String? @@ -25,8 +26,9 @@ struct VideoSource { self.isAsset = false self.shouldCache = false self.requestHeaders = nil - self.startTime = nil - self.endTime = nil + self.startPosition = nil + self.cropStart = nil + self.cropEnd = nil self.title = nil self.subtitle = nil self.description = nil @@ -40,8 +42,9 @@ struct VideoSource { self.isAsset = json["isAsset"] as? Bool ?? false self.shouldCache = json["shouldCache"] as? Bool ?? false self.requestHeaders = json["requestHeaders"] as? Dictionary - self.startTime = json["startTime"] as? Int64 - self.endTime = json["endTime"] as? Int64 + self.startPosition = json["startPosition"] as? Int64 + self.cropStart = json["cropStart"] as? Int64 + self.cropEnd = json["cropEnd"] as? Int64 self.title = json["title"] as? String self.subtitle = json["subtitle"] as? String self.description = json["description"] as? String diff --git a/ios/Video/Features/RCTVideoUtils.swift b/ios/Video/Features/RCTVideoUtils.swift index 3e0ef3bb..bde3f309 100644 --- a/ios/Video/Features/RCTVideoUtils.swift +++ b/ios/Video/Features/RCTVideoUtils.swift @@ -19,8 +19,8 @@ enum RCTVideoUtils { return 0 } - if (source?.startTime != nil && source?.endTime != nil) { - return NSNumber(value: (Float64(source?.endTime ?? 0) - Float64(source?.startTime ?? 0)) / 1000) + if (source?.cropStart != nil && source?.cropEnd != nil) { + return NSNumber(value: (Float64(source?.cropEnd ?? 0) - Float64(source?.cropStart ?? 0)) / 1000) } var effectiveTimeRange:CMTimeRange? @@ -35,8 +35,8 @@ enum RCTVideoUtils { if let effectiveTimeRange = effectiveTimeRange { let playableDuration:Float64 = CMTimeGetSeconds(CMTimeRangeGetEnd(effectiveTimeRange)) if playableDuration > 0 { - if (source?.startTime != nil) { - return NSNumber(value: (playableDuration - Float64(source?.startTime ?? 0) / 1000)) + if (source?.cropStart != nil) { + return NSNumber(value: (playableDuration - Float64(source?.cropStart ?? 0) / 1000)) } return playableDuration as NSNumber diff --git a/ios/Video/RCTVideo.swift b/ios/Video/RCTVideo.swift index 3cf6a019..dfdde5c5 100644 --- a/ios/Video/RCTVideo.swift +++ b/ios/Video/RCTVideo.swift @@ -66,6 +66,7 @@ class RCTVideo: UIView, RCTVideoPlayerViewControllerDelegate, RCTPlayerObserverH private var _filterEnabled:Bool = false private var _presentingViewController:UIViewController? private var _pictureInPictureEnabled = false + private var _startPosition:Float64 = -1 /* IMA Ads */ private var _adTagUrl:String? @@ -251,8 +252,8 @@ class RCTVideo: UIView, RCTVideoPlayerViewControllerDelegate, RCTPlayerObserverH } var currentTime = _player?.currentTime() - if (currentTime != nil && _source?.startTime != nil) { - currentTime = CMTimeSubtract(currentTime!, CMTimeMake(value: _source?.startTime ?? 0, timescale: 1000)) + if (currentTime != nil && _source?.cropStart != nil) { + currentTime = CMTimeSubtract(currentTime!, CMTimeMake(value: _source?.cropStart ?? 0, timescale: 1000)) } let currentPlaybackTime = _player?.currentItem?.currentDate() let duration = CMTimeGetSeconds(playerDuration) @@ -316,6 +317,10 @@ class RCTVideo: UIView, RCTVideoPlayerViewControllerDelegate, RCTPlayerObserverH throw NSError(domain: "", code: 0, userInfo: nil) } + if let startPosition = self._source?.startPosition { + self._startPosition = Float64(startPosition) / 1000 + } + #if USE_VIDEO_CACHING if self._videoCache.shouldCache(source:source, textTracks:self._textTracks) { return self._videoCache.playerItemForSourceUsingCache(uri: source.uri, assetOptions:assetOptions) @@ -341,7 +346,7 @@ class RCTVideo: UIView, RCTVideoPlayerViewControllerDelegate, RCTPlayerObserverH self._playerItem = playerItem self._playerObserver.playerItem = self._playerItem self.setPreferredForwardBufferDuration(self._preferredForwardBufferDuration) - self.setPlaybackRange(playerItem, withVideoStart: self._source?.startTime, withVideoEnd: self._source?.endTime) + self.setPlaybackRange(playerItem, withVideoStart: self._source?.cropStart, withVideoEnd: self._source?.cropEnd) self.setFilter(self._filterName) if let maxBitRate = self._maxBitRate { self._playerItem?.preferredPeakBitRate = Double(maxBitRate) @@ -601,6 +606,7 @@ class RCTVideo: UIView, RCTVideoPlayerViewControllerDelegate, RCTPlayerObserverH _pendingSeek = false } + @objc func setRate(_ rate:Float) { _rate = rate @@ -1177,6 +1183,14 @@ class RCTVideo: UIView, RCTVideoPlayerViewControllerDelegate, RCTPlayerObserverH _pendingSeek = false } + if _startPosition >= 0 { + setSeek([ + "time": NSNumber(value: _startPosition), + "tolerance": NSNumber(value: 100) + ]) + _startPosition = -1 + } + if _videoLoadStarted { let audioTracks = RCTVideoUtils.getAudioTrackInfo(_player) let textTracks = RCTVideoUtils.getTextTrackInfo(_player).map(\.json) diff --git a/src/Video.tsx b/src/Video.tsx index a0193a87..7f99651e 100644 --- a/src/Video.tsx +++ b/src/Video.tsx @@ -147,9 +147,10 @@ const Video = forwardRef( type: resolvedSource.type || '', mainVer: resolvedSource.mainVer || 0, patchVer: resolvedSource.patchVer || 0, - requestHeaders: resolvedSource?.headers || {}, - startTime: resolvedSource.startTime || 0, - endTime: resolvedSource.endTime, + requestHeaders: resolvedSource.headers || {}, + startPosition: resolvedSource.startPosition ?? -1, + cropStart: resolvedSource.cropStart || 0, + cropEnd: resolvedSource.cropEnd, title: resolvedSource.title, subtitle: resolvedSource.subtitle, description: resolvedSource.description, diff --git a/src/VideoNativeComponent.ts b/src/VideoNativeComponent.ts index 0647af3d..54634e35 100644 --- a/src/VideoNativeComponent.ts +++ b/src/VideoNativeComponent.ts @@ -23,8 +23,9 @@ type VideoSrc = Readonly<{ mainVer?: number; patchVer?: number; requestHeaders?: Headers; - startTime?: number; - endTime?: number; + startPosition?: number; + cropStart?: number; + cropEnd?: number; title?: string; subtitle?: string; description?: string; diff --git a/src/types/video.ts b/src/types/video.ts index 013ba245..34b0b3d5 100644 --- a/src/types/video.ts +++ b/src/types/video.ts @@ -15,8 +15,9 @@ export type ReactVideoSourceProperties = { mainVer?: number; patchVer?: number; headers?: Headers; - startTime?: number; - endTime?: number; + startPosition?: number; + cropStart?: number; + cropEnd?: number; title?: string; subtitle?: string; description?: string;