From 7da44c238f2d385d57b2d87dd31150bb93011fd4 Mon Sep 17 00:00:00 2001 From: Yavor Ivanov Date: Mon, 6 Feb 2023 21:43:14 +0200 Subject: [PATCH 001/117] iOS playback range --- Video.js | 2 ++ ios/Video/DataStructures/VideoSource.swift | 6 ++++++ ios/Video/RCTVideo.swift | 10 ++++++++++ 3 files changed, 18 insertions(+) diff --git a/Video.js b/Video.js index ac6bc085a1..87aa103a3f 100644 --- a/Video.js +++ b/Video.js @@ -342,6 +342,8 @@ export default class Video extends Component { mainVer: source.mainVer || 0, patchVer: source.patchVer || 0, requestHeaders: source.headers ? this.stringsOnlyObject(source.headers) : {}, + startTime: source.startTime, + endTime: source.endTime }, onVideoLoadStart: this._onLoadStart, onVideoPlaybackStateChanged: this._onPlaybackStateChanged, diff --git a/ios/Video/DataStructures/VideoSource.swift b/ios/Video/DataStructures/VideoSource.swift index 20ab7158d4..7cb7c38bc2 100644 --- a/ios/Video/DataStructures/VideoSource.swift +++ b/ios/Video/DataStructures/VideoSource.swift @@ -6,6 +6,8 @@ struct VideoSource { let isAsset: Bool let shouldCache: Bool let requestHeaders: Dictionary? + let startTime: Int64? + let endTime: Int64? let json: NSDictionary? @@ -18,6 +20,8 @@ struct VideoSource { self.isAsset = false self.shouldCache = false self.requestHeaders = nil + self.startTime = nil + self.endTime = nil return } self.json = json @@ -27,5 +31,7 @@ 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 } } diff --git a/ios/Video/RCTVideo.swift b/ios/Video/RCTVideo.swift index baca4d1af9..899749482f 100644 --- a/ios/Video/RCTVideo.swift +++ b/ios/Video/RCTVideo.swift @@ -292,6 +292,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.setFilter(self._filterName) if let maxBitRate = self._maxBitRate { self._playerItem?.preferredPeakBitRate = Double(maxBitRate) @@ -533,6 +534,15 @@ class RCTVideo: UIView, RCTVideoPlayerViewControllerDelegate, RCTPlayerObserverH // Fallback on earlier versions } } + + func setPlaybackRange(_ item:AVPlayerItem!, withVideoStart videoStart:Int64?, withVideoEnd videoEnd:Int64?) { + if (videoStart != nil) { + item.reversePlaybackEndTime = CMTimeMake(value: videoStart!, timescale: 1000) + } + if (videoEnd != nil) { + item.forwardPlaybackEndTime = CMTimeMake(value: videoEnd!, timescale: 1000) + } + } func applyModifiers() { From 0f6057bea5708246d578e13cc992f19ddb53d8b0 Mon Sep 17 00:00:00 2001 From: Yavor Ivanov Date: Mon, 6 Feb 2023 23:31:14 +0200 Subject: [PATCH 002/117] Seeking to start time --- ios/Video/RCTVideo.swift | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/ios/Video/RCTVideo.swift b/ios/Video/RCTVideo.swift index 899749482f..7427d51ec8 100644 --- a/ios/Video/RCTVideo.swift +++ b/ios/Video/RCTVideo.swift @@ -537,7 +537,10 @@ class RCTVideo: UIView, RCTVideoPlayerViewControllerDelegate, RCTPlayerObserverH func setPlaybackRange(_ item:AVPlayerItem!, withVideoStart videoStart:Int64?, withVideoEnd videoEnd:Int64?) { if (videoStart != nil) { - item.reversePlaybackEndTime = CMTimeMake(value: videoStart!, timescale: 1000) + let start = CMTimeMake(value: videoStart!, timescale: 1000) + item.reversePlaybackEndTime = start + _pendingSeekTime = Float(CMTimeGetSeconds(start)) + _pendingSeek = true } if (videoEnd != nil) { item.forwardPlaybackEndTime = CMTimeMake(value: videoEnd!, timescale: 1000) From fe5fc543b948a519b143f4fd82c0a371a2b331cb Mon Sep 17 00:00:00 2001 From: Yavor Ivanov Date: Tue, 7 Feb 2023 22:50:54 +0200 Subject: [PATCH 003/117] Corrected currentTime & playableDuration when using start & end time --- ios/Video/Features/RCTVideoUtils.swift | 10 +++++++++- ios/Video/RCTVideo.swift | 7 +++++-- 2 files changed, 14 insertions(+), 3 deletions(-) diff --git a/ios/Video/Features/RCTVideoUtils.swift b/ios/Video/Features/RCTVideoUtils.swift index 50df8e3f14..5ff5928c64 100644 --- a/ios/Video/Features/RCTVideoUtils.swift +++ b/ios/Video/Features/RCTVideoUtils.swift @@ -12,13 +12,17 @@ enum RCTVideoUtils { * * \returns The playable duration of the current player item in seconds. */ - static func calculatePlayableDuration(_ player:AVPlayer?) -> NSNumber { + static func calculatePlayableDuration(_ player:AVPlayer?, withSource source:VideoSource?) -> NSNumber { guard let player = player, let video:AVPlayerItem = player.currentItem, video.status == AVPlayerItem.Status.readyToPlay else { return 0 } + if (source?.startTime != nil && source?.endTime != nil) { + return NSNumber(value: (Float64(source?.endTime ?? 0) - Float64(source?.startTime ?? 0)) / 1000) + } + var effectiveTimeRange:CMTimeRange? for (_, value) in video.loadedTimeRanges.enumerated() { let timeRange:CMTimeRange = value.timeRangeValue @@ -31,6 +35,10 @@ 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)) + } + return playableDuration as NSNumber } } diff --git a/ios/Video/RCTVideo.swift b/ios/Video/RCTVideo.swift index 7427d51ec8..0381124203 100644 --- a/ios/Video/RCTVideo.swift +++ b/ios/Video/RCTVideo.swift @@ -207,7 +207,10 @@ class RCTVideo: UIView, RCTVideoPlayerViewControllerDelegate, RCTPlayerObserverH return } - let currentTime = _player?.currentTime() + var currentTime = _player?.currentTime() + if (currentTime != nil && _source?.startTime != nil) { + currentTime = CMTimeSubtract(currentTime!, CMTimeMake(value: _source?.startTime ?? 0, timescale: 1000)) + } let currentPlaybackTime = _player?.currentItem?.currentDate() let duration = CMTimeGetSeconds(playerDuration) let currentTimeSecs = CMTimeGetSeconds(currentTime ?? .zero) @@ -223,7 +226,7 @@ class RCTVideo: UIView, RCTVideoPlayerViewControllerDelegate, RCTPlayerObserverH } onVideoProgress?([ "currentTime": NSNumber(value: Float(currentTimeSecs)), - "playableDuration": RCTVideoUtils.calculatePlayableDuration(_player), + "playableDuration": RCTVideoUtils.calculatePlayableDuration(_player, withSource: _source), "atValue": NSNumber(value: currentTime?.value ?? .zero), "currentPlaybackTime": NSNumber(value: NSNumber(value: floor(currentPlaybackTime?.timeIntervalSince1970 ?? 0 * 1000)).int64Value), "target": reactTag, From 6ca0ab3834c947ddfc0c958cee6c29f4fed603be Mon Sep 17 00:00:00 2001 From: Radin Gospodinov Date: Thu, 9 Feb 2023 09:38:05 +0200 Subject: [PATCH 004/117] Android range playback. --- .../exoplayer/ReactExoplayerView.java | 50 +++++++++++++++---- .../exoplayer/ReactExoplayerViewManager.java | 9 +++- 2 files changed, 48 insertions(+), 11 deletions(-) diff --git a/android/src/main/java/com/brentvatne/exoplayer/ReactExoplayerView.java b/android/src/main/java/com/brentvatne/exoplayer/ReactExoplayerView.java index 0c0fbc1747..0acc0d007a 100644 --- a/android/src/main/java/com/brentvatne/exoplayer/ReactExoplayerView.java +++ b/android/src/main/java/com/brentvatne/exoplayer/ReactExoplayerView.java @@ -93,6 +93,7 @@ import com.google.android.exoplayer2.ext.ima.ImaAdsLoader; import com.google.android.exoplayer2.source.ads.AdsMediaSource; import com.google.android.exoplayer2.source.DefaultMediaSourceFactory; +import com.google.android.exoplayer2.source.ClippingMediaSource; import com.google.common.collect.ImmutableList; import java.net.CookieHandler; @@ -181,6 +182,10 @@ 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 String extension; private boolean repeat; private String audioTrackType; @@ -669,7 +674,7 @@ private DrmSessionManager initializePlayerDrm(ReactExoplayerView self) { private void initializePlayerSource(ReactExoplayerView self, DrmSessionManager drmSessionManager) { ArrayList mediaSourceList = buildTextSources(); - MediaSource videoSource = buildMediaSource(self.srcUri, self.extension, drmSessionManager); + MediaSource videoSource = buildMediaSource(self.srcUri, self.extension, drmSessionManager, startTimeMs, endTimeMs); MediaSource mediaSourceWithAds = null; if (adTagUrl != null) { MediaSource.Factory mediaSourceFactory = new DefaultMediaSourceFactory(mediaDataSourceFactory) @@ -764,7 +769,13 @@ private DrmSessionManager buildDrmSessionManager(UUID uuid, String licenseUrl, S } } - private MediaSource buildMediaSource(Uri uri, String overrideExtension, DrmSessionManager drmSessionManager) { + private MediaSource buildMediaSource( + Uri uri, + String overrideExtension, + DrmSessionManager drmSessionManager, + long startTimeMs, + long endTimeMs) { + if (uri == null) { throw new IllegalStateException("Invalid video uri"); } @@ -781,7 +792,7 @@ private MediaSource buildMediaSource(Uri uri, String overrideExtension, DrmSessi } MediaItem mediaItem = mediaItemBuilder.build(); - + MediaSource mediaSource = null; DrmSessionManagerProvider drmProvider = null; if (drmSessionManager != null) { drmProvider = new DrmSessionManagerProvider() { @@ -795,39 +806,50 @@ public DrmSessionManager get(MediaItem mediaItem) { } switch (type) { case CONTENT_TYPE_SS: - return new SsMediaSource.Factory( + mediaSource = SsMediaSource.Factory( new DefaultSsChunkSource.Factory(mediaDataSourceFactory), buildDataSourceFactory(false) ).setDrmSessionManagerProvider(drmProvider) .setLoadErrorHandlingPolicy( config.buildLoadErrorHandlingPolicy(minLoadRetryCount) ).createMediaSource(mediaItem); + break; case CONTENT_TYPE_DASH: - return new DashMediaSource.Factory( + mediaSource = DashMediaSource.Factory( new DefaultDashChunkSource.Factory(mediaDataSourceFactory), buildDataSourceFactory(false) ).setDrmSessionManagerProvider(drmProvider) .setLoadErrorHandlingPolicy( config.buildLoadErrorHandlingPolicy(minLoadRetryCount) ).createMediaSource(mediaItem); + break; case CONTENT_TYPE_HLS: - return new HlsMediaSource.Factory( + mediaSource = HlsMediaSource.Factory( mediaDataSourceFactory ).setDrmSessionManagerProvider(drmProvider) .setLoadErrorHandlingPolicy( config.buildLoadErrorHandlingPolicy(minLoadRetryCount) ).createMediaSource(mediaItem); + break; case CONTENT_TYPE_OTHER: - return new ProgressiveMediaSource.Factory( + mediaSource = ProgressiveMediaSource.Factory( mediaDataSourceFactory ).setDrmSessionManagerProvider(drmProvider) .setLoadErrorHandlingPolicy( config.buildLoadErrorHandlingPolicy(minLoadRetryCount) ).createMediaSource(mediaItem); + break; default: { throw new IllegalStateException("Unsupported type: " + type); } } + + if(startTimeMs >= 0 && endTimeMs >= 0) + { + return new ClippingMediaSource(mediaSource, startTimeMs * 1000, endTimeMs * 1000); + } + + return mediaSource; } private ArrayList buildTextSources() { @@ -1465,11 +1487,19 @@ public void onMetadata(Metadata metadata) { // ReactExoplayerViewManager public api - public void setSrc(final Uri uri, final String extension, Map headers) { + public void setSrc( + final Uri uri, + final long startTimeMs, + final long endTimeMs, + final String extension, + Map headers) { + if (uri != null) { - boolean isSourceEqual = uri.equals(srcUri); + boolean isSourceEqual = uri.equals(srcUri) && startTimeMs == this.startTimeMs && endTimeMs == this.endTimeMs; hasDrmFailed = false; this.srcUri = uri; + this.startTimeMs = startTimeMs; + this.endTimeMs = endTimeMs; this.extension = extension; this.requestHeaders = headers; this.mediaDataSourceFactory = @@ -1487,6 +1517,8 @@ public void clearSrc() { player.stop(); player.clearMediaItems(); this.srcUri = null; + this.startTimeMs = -1; + this.endTimeMs = -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 8da025e74e..6f2b129697 100644 --- a/android/src/main/java/com/brentvatne/exoplayer/ReactExoplayerViewManager.java +++ b/android/src/main/java/com/brentvatne/exoplayer/ReactExoplayerViewManager.java @@ -28,9 +28,12 @@ public class ReactExoplayerViewManager extends ViewGroupManager { private static final String REACT_CLASS = "RCTVideo"; - private static final String PROP_SRC = "src"; private static final String PROP_SRC_URI = "uri"; + + private static final String PROP_SRC_START_TIME = "startTime"; + + private static final String PROP_SRC_END_TIME = "endTime"; private static final String PROP_AD_TAG_URL = "adTagUrl"; private static final String PROP_SRC_TYPE = "type"; private static final String PROP_DRM = "drm"; @@ -152,6 +155,8 @@ public void setDRM(final ReactExoplayerView videoView, @Nullable ReadableMap drm public void setSrc(final ReactExoplayerView videoView, @Nullable ReadableMap src) { Context context = videoView.getContext().getApplicationContext(); String uriString = src.hasKey(PROP_SRC_URI) ? src.getString(PROP_SRC_URI) : null; + int startTimeMs = src.hasKey(PROP_SRC_START_TIME) ? src.getInt(PROP_SRC_START_TIME) : -1; + int endTimeMs = src.hasKey(PROP_SRC_END_TIME) ? src.getInt(PROP_SRC_END_TIME) : -1; String extension = src.hasKey(PROP_SRC_TYPE) ? src.getString(PROP_SRC_TYPE) : null; Map headers = src.hasKey(PROP_SRC_HEADERS) ? toStringMap(src.getMap(PROP_SRC_HEADERS)) : null; @@ -164,7 +169,7 @@ public void setSrc(final ReactExoplayerView videoView, @Nullable ReadableMap src Uri srcUri = Uri.parse(uriString); if (srcUri != null) { - videoView.setSrc(srcUri, extension, headers); + videoView.setSrc(srcUri, startTimeMs, endTimeMs, extension, headers); } } else { int identifier = context.getResources().getIdentifier( From a56b4d148c2f06e7dc8a3f7c047a43dca8da57f5 Mon Sep 17 00:00:00 2001 From: Yavor Ivanov Date: Thu, 9 Feb 2023 13:53:06 +0200 Subject: [PATCH 005/117] Fixed android build error --- .../java/com/brentvatne/exoplayer/ReactExoplayerView.java | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/android/src/main/java/com/brentvatne/exoplayer/ReactExoplayerView.java b/android/src/main/java/com/brentvatne/exoplayer/ReactExoplayerView.java index 0acc0d007a..00983c4e7d 100644 --- a/android/src/main/java/com/brentvatne/exoplayer/ReactExoplayerView.java +++ b/android/src/main/java/com/brentvatne/exoplayer/ReactExoplayerView.java @@ -806,7 +806,7 @@ public DrmSessionManager get(MediaItem mediaItem) { } switch (type) { case CONTENT_TYPE_SS: - mediaSource = SsMediaSource.Factory( + mediaSource = new SsMediaSource.Factory( new DefaultSsChunkSource.Factory(mediaDataSourceFactory), buildDataSourceFactory(false) ).setDrmSessionManagerProvider(drmProvider) @@ -815,7 +815,7 @@ public DrmSessionManager get(MediaItem mediaItem) { ).createMediaSource(mediaItem); break; case CONTENT_TYPE_DASH: - mediaSource = DashMediaSource.Factory( + mediaSource = new DashMediaSource.Factory( new DefaultDashChunkSource.Factory(mediaDataSourceFactory), buildDataSourceFactory(false) ).setDrmSessionManagerProvider(drmProvider) @@ -824,7 +824,7 @@ public DrmSessionManager get(MediaItem mediaItem) { ).createMediaSource(mediaItem); break; case CONTENT_TYPE_HLS: - mediaSource = HlsMediaSource.Factory( + mediaSource = new HlsMediaSource.Factory( mediaDataSourceFactory ).setDrmSessionManagerProvider(drmProvider) .setLoadErrorHandlingPolicy( @@ -832,7 +832,7 @@ public DrmSessionManager get(MediaItem mediaItem) { ).createMediaSource(mediaItem); break; case CONTENT_TYPE_OTHER: - mediaSource = ProgressiveMediaSource.Factory( + mediaSource = new ProgressiveMediaSource.Factory( mediaDataSourceFactory ).setDrmSessionManagerProvider(drmProvider) .setLoadErrorHandlingPolicy( From 63625b3ce35ded91dfeb9cc32b87f18c1c553567 Mon Sep 17 00:00:00 2001 From: Yavor Ivanov Date: Mon, 13 Feb 2023 16:07:09 +0200 Subject: [PATCH 006/117] Adding documentation --- API.md | 11 +++++++++++ CHANGELOG.md | 3 +++ 2 files changed, 14 insertions(+) diff --git a/API.md b/API.md index a769aeba89..1a01208507 100644 --- a/API.md +++ b/API.md @@ -916,6 +916,17 @@ The following other types are supported on some platforms, but aren't fully docu `content://, ms-appx://, ms-appdata://, assets-library://` +##### Playing only a portion of the video (start & end time) + +Provide an optional `startTime` and/or `endTime` for the video. Value is in milliseconds. Useful when you want to play only a portion of a large video. + +Example +``` +source={{ startTime: 36012, endTime: 48500 }} +``` + +Platforms: iOS, Android + #### subtitleStyle Property | Description | Platforms diff --git a/CHANGELOG.md b/CHANGELOG.md index 71b54f1c63..f46f893e10 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,8 @@ ## Changelog +### Version 6.0.0-alpha.6 +- Feature: Video range support [#3030](https://github.com/react-native-video/react-native-video/pull/3030) + ### Version 6.0.0-alpha.5 - iOS: ensure controls are not displayed when disabled by user [#3017](https://github.com/react-native-video/react-native-video/pull/3017) From e3fb49cebb51c23c1cc4f7b0a270c600b866f363 Mon Sep 17 00:00:00 2001 From: Yavor Ivanov Date: Mon, 13 Feb 2023 17:30:24 +0200 Subject: [PATCH 007/117] Android formatting fixes --- .../exoplayer/ReactExoplayerView.java | 18 ++---------------- .../exoplayer/ReactExoplayerViewManager.java | 2 -- 2 files changed, 2 insertions(+), 18 deletions(-) diff --git a/android/src/main/java/com/brentvatne/exoplayer/ReactExoplayerView.java b/android/src/main/java/com/brentvatne/exoplayer/ReactExoplayerView.java index 00983c4e7d..6e538fe802 100644 --- a/android/src/main/java/com/brentvatne/exoplayer/ReactExoplayerView.java +++ b/android/src/main/java/com/brentvatne/exoplayer/ReactExoplayerView.java @@ -182,9 +182,7 @@ 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 String extension; private boolean repeat; @@ -769,13 +767,7 @@ private DrmSessionManager buildDrmSessionManager(UUID uuid, String licenseUrl, S } } - private MediaSource buildMediaSource( - Uri uri, - String overrideExtension, - DrmSessionManager drmSessionManager, - long startTimeMs, - long endTimeMs) { - + private MediaSource buildMediaSource(Uri uri, String overrideExtension, DrmSessionManager drmSessionManager, long startTimeMs, long endTimeMs) { if (uri == null) { throw new IllegalStateException("Invalid video uri"); } @@ -1487,13 +1479,7 @@ public void onMetadata(Metadata metadata) { // 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 startTimeMs, final long endTimeMs, final String extension, Map headers) { if (uri != null) { boolean isSourceEqual = uri.equals(srcUri) && startTimeMs == this.startTimeMs && endTimeMs == this.endTimeMs; hasDrmFailed = false; diff --git a/android/src/main/java/com/brentvatne/exoplayer/ReactExoplayerViewManager.java b/android/src/main/java/com/brentvatne/exoplayer/ReactExoplayerViewManager.java index 6f2b129697..e0468985f2 100644 --- a/android/src/main/java/com/brentvatne/exoplayer/ReactExoplayerViewManager.java +++ b/android/src/main/java/com/brentvatne/exoplayer/ReactExoplayerViewManager.java @@ -30,9 +30,7 @@ public class ReactExoplayerViewManager extends ViewGroupManager Date: Mon, 13 Feb 2023 17:57:13 +0200 Subject: [PATCH 008/117] android support for partial video range --- .../java/com/brentvatne/exoplayer/ReactExoplayerView.java | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/android/src/main/java/com/brentvatne/exoplayer/ReactExoplayerView.java b/android/src/main/java/com/brentvatne/exoplayer/ReactExoplayerView.java index 00983c4e7d..8e4e98506e 100644 --- a/android/src/main/java/com/brentvatne/exoplayer/ReactExoplayerView.java +++ b/android/src/main/java/com/brentvatne/exoplayer/ReactExoplayerView.java @@ -4,6 +4,7 @@ import static com.google.android.exoplayer2.C.CONTENT_TYPE_HLS; import static com.google.android.exoplayer2.C.CONTENT_TYPE_OTHER; import static com.google.android.exoplayer2.C.CONTENT_TYPE_SS; +import static com.google.android.exoplayer2.C.TIME_END_OF_SOURCE; import android.annotation.SuppressLint; import android.app.Activity; @@ -844,9 +845,13 @@ public DrmSessionManager get(MediaItem mediaItem) { } } - if(startTimeMs >= 0 && endTimeMs >= 0) + 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); } return mediaSource; From dd4e50fca31fa92c2e09388fe9eef4270ece1b13 Mon Sep 17 00:00:00 2001 From: Yavor Ivanov Date: Mon, 13 Feb 2023 18:14:20 +0200 Subject: [PATCH 009/117] updated documentation --- API.md | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/API.md b/API.md index 1a01208507..ff968f067b 100644 --- a/API.md +++ b/API.md @@ -922,7 +922,11 @@ Provide an optional `startTime` and/or `endTime` for the video. Value is in mill Example ``` -source={{ startTime: 36012, endTime: 48500 }} +source={{ uri: 'https://bitdash-a.akamaihd.net/content/sintel/hls/playlist.m3u8', startTime: 36012, endTime: 48500 }} + +source={{ uri: 'https://bitdash-a.akamaihd.net/content/sintel/hls/playlist.m3u8', startTime: 36012 }} + +source={{ uri: 'https://bitdash-a.akamaihd.net/content/sintel/hls/playlist.m3u8', endTime: 48500 }} ``` Platforms: iOS, Android From 2fc71935805e3e87a8921af6e9cfa18b8ac7de47 Mon Sep 17 00:00:00 2001 From: Roman Melnyk Date: Fri, 3 Mar 2023 16:47:05 +0200 Subject: [PATCH 010/117] Print error for configureAudio method try catch blocks. Add fallback for error: 'what' (AVAudioSessionErrorCodeUnspecified). --- ios/Video/Features/RCTPlayerOperations.swift | 29 +++++++++++++++----- 1 file changed, 22 insertions(+), 7 deletions(-) diff --git a/ios/Video/Features/RCTPlayerOperations.swift b/ios/Video/Features/RCTPlayerOperations.swift index 8b76fafc5f..453a0a0c05 100644 --- a/ios/Video/Features/RCTPlayerOperations.swift +++ b/ios/Video/Features/RCTPlayerOperations.swift @@ -192,36 +192,51 @@ enum RCTPlayerOperations { } static func configureAudio(ignoreSilentSwitch:String, mixWithOthers:String) { - let session:AVAudioSession! = AVAudioSession.sharedInstance() + let audioSession:AVAudioSession! = AVAudioSession.sharedInstance() var category:AVAudioSession.Category? = nil var options:AVAudioSession.CategoryOptions? = nil - + if (ignoreSilentSwitch == "ignore") { category = AVAudioSession.Category.playback } else if (ignoreSilentSwitch == "obey") { category = AVAudioSession.Category.ambient } - + if (mixWithOthers == "mix") { options = .mixWithOthers } else if (mixWithOthers == "duck") { options = .duckOthers } - + if let category = category, let options = options { do { - try session.setCategory(category, options: options) + try audioSession.setCategory(category, options: options) } catch { + debugPrint("[RCTPlayerOperations] Problem setting up AVAudioSession category and options. Error: \(error).") + // Handle specific set category abd option combination error + // setCategory:AVAudioSessionCategoryPlayback withOptions:mixWithOthers || duckOthers + // Failed to set category, error: 'what' Error Domain=NSOSStatusErrorDomain + // https://developer.apple.com/forums/thread/714598 + if #available(iOS 16.0, *) { + do { + debugPrint("[RCTPlayerOperations] Reseting AVAudioSession category to playAndRecord with defaultToSpeaker options.") + try audioSession.setCategory(AVAudioSession.Category.playAndRecord, options: AVAudioSession.CategoryOptions.defaultToSpeaker) + } catch { + debugPrint("[RCTPlayerOperations] Reseting AVAudioSession category and options problem. Error: \(error).") + } + } } } else if let category = category, options == nil { do { - try session.setCategory(category) + try audioSession.setCategory(category) } catch { + debugPrint("[RCTPlayerOperations] Problem setting up AVAudioSession category. Error: \(error).") } } else if category == nil, let options = options { do { - try session.setCategory(session.category, options: options) + try audioSession.setCategory(audioSession.category, options: options) } catch { + debugPrint("[RCTPlayerOperations] Problem setting up AVAudioSession options. Error: \(error).") } } } From e3b685b8e36cdaf60d5e0e9417d04a091f3c07bd Mon Sep 17 00:00:00 2001 From: Yavor Ivanov Date: Mon, 13 Mar 2023 16:14:04 +0200 Subject: [PATCH 011/117] Update Video.js --- Video.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Video.js b/Video.js index 87aa103a3f..03fc95bf9c 100644 --- a/Video.js +++ b/Video.js @@ -342,7 +342,7 @@ export default class Video extends Component { mainVer: source.mainVer || 0, patchVer: source.patchVer || 0, requestHeaders: source.headers ? this.stringsOnlyObject(source.headers) : {}, - startTime: source.startTime, + startTime: source.startTime || 0, endTime: source.endTime }, onVideoLoadStart: this._onLoadStart, From 2ef2b8eb98b96adb5f8cd645686a5ba7f8a628bc Mon Sep 17 00:00:00 2001 From: Sunbreak Date: Wed, 15 Mar 2023 00:09:31 +0800 Subject: [PATCH 012/117] fix: remove undocumented currentTime property --- CHANGELOG.md | 4 ++++ Video.js | 1 - ios/Video/RCTVideo.swift | 14 ++++---------- ios/Video/RCTVideoManager.m | 1 - 4 files changed, 8 insertions(+), 12 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 71b54f1c63..b3ad99b8bf 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,9 @@ ## Changelog +### Version 6.0.0-alpha.6 + +- iOS: remove undocumented `currentTime` property [#3064](https://github.com/react-native-video/react-native-video/pull/3064) + ### Version 6.0.0-alpha.5 - iOS: ensure controls are not displayed when disabled by user [#3017](https://github.com/react-native-video/react-native-video/pull/3017) diff --git a/Video.js b/Video.js index ac6bc085a1..bbd7d621b5 100644 --- a/Video.js +++ b/Video.js @@ -520,7 +520,6 @@ Video.propTypes = { disableBuffering: PropTypes.bool, controls: PropTypes.bool, audioOnly: PropTypes.bool, - currentTime: PropTypes.number, fullscreenAutorotate: PropTypes.bool, fullscreenOrientation: PropTypes.oneOf(['all', 'landscape', 'portrait']), progressUpdateInterval: PropTypes.number, diff --git a/ios/Video/RCTVideo.swift b/ios/Video/RCTVideo.swift index be1c0981e5..fee5eb6089 100644 --- a/ios/Video/RCTVideo.swift +++ b/ios/Video/RCTVideo.swift @@ -448,15 +448,6 @@ class RCTVideo: UIView, RCTVideoPlayerViewControllerDelegate, RCTPlayerObserverH _paused = paused } - @objc - func setCurrentTime(_ currentTime:Float) { - let info:NSDictionary = [ - "time": NSNumber(value: currentTime), - "tolerance": NSNumber(value: 100) - ] - setSeek(info) - } - @objc func setSeek(_ info:NSDictionary!) { let seekTime:NSNumber! = info["time"] as! NSNumber @@ -996,7 +987,10 @@ class RCTVideo: UIView, RCTVideoPlayerViewControllerDelegate, RCTPlayerObserverH } if _pendingSeek { - setCurrentTime(_pendingSeekTime) + setSeek([ + "time": NSNumber(value: _pendingSeekTime), + "tolerance": NSNumber(value: 100) + ]) _pendingSeek = false } diff --git a/ios/Video/RCTVideoManager.m b/ios/Video/RCTVideoManager.m index 2781656be7..0d503d1419 100644 --- a/ios/Video/RCTVideoManager.m +++ b/ios/Video/RCTVideoManager.m @@ -27,7 +27,6 @@ @interface RCT_EXTERN_MODULE(RCTVideoManager, RCTViewManager) RCT_EXPORT_VIEW_PROPERTY(mixWithOthers, NSString); RCT_EXPORT_VIEW_PROPERTY(rate, float); RCT_EXPORT_VIEW_PROPERTY(seek, NSDictionary); -RCT_EXPORT_VIEW_PROPERTY(currentTime, float); RCT_EXPORT_VIEW_PROPERTY(fullscreen, BOOL); RCT_EXPORT_VIEW_PROPERTY(fullscreenAutorotate, BOOL); RCT_EXPORT_VIEW_PROPERTY(fullscreenOrientation, NSString); From d9e4b1efecd3cc2f6092e0e541e886403f59e849 Mon Sep 17 00:00:00 2001 From: Sunbreak Date: Wed, 15 Mar 2023 09:58:16 +0800 Subject: [PATCH 013/117] fix: remove dummy scaleX/Y & translateX/Y property --- Video.js | 5 ----- 1 file changed, 5 deletions(-) diff --git a/Video.js b/Video.js index ac6bc085a1..0f6bb32b6a 100644 --- a/Video.js +++ b/Video.js @@ -563,11 +563,6 @@ Video.propTypes = { onReceiveAdEvent: PropTypes.func, /* Required by react-native */ - scaleX: PropTypes.number, - scaleY: PropTypes.number, - translateX: PropTypes.number, - translateY: PropTypes.number, - rotation: PropTypes.number, ...ViewPropTypes, }; From 822f8c077476383fbadd2a47c3c4d27d9f8ee082 Mon Sep 17 00:00:00 2001 From: Sunbreak Date: Wed, 15 Mar 2023 09:54:48 +0800 Subject: [PATCH 014/117] fix: remove dummy needsToRestoreUserInterfaceForPictureInPictureStop --- Video.js | 1 - 1 file changed, 1 deletion(-) diff --git a/Video.js b/Video.js index ac6bc085a1..2aad451ab6 100644 --- a/Video.js +++ b/Video.js @@ -557,7 +557,6 @@ Video.propTypes = { onAudioFocusChanged: PropTypes.func, onAudioBecomingNoisy: PropTypes.func, onPictureInPictureStatusChanged: PropTypes.func, - needsToRestoreUserInterfaceForPictureInPictureStop: PropTypes.func, onExternalPlaybackChange: PropTypes.func, adTagUrl: PropTypes.string, onReceiveAdEvent: PropTypes.func, From 9519c7bae76486e41b6ab2cf31afa3b8280448dd Mon Sep 17 00:00:00 2001 From: Francesco Benigno Date: Tue, 28 Mar 2023 13:14:48 +0200 Subject: [PATCH 015/117] set the ad volume to 0 when the player is muted on iOS --- ios/Video/Features/RCTIMAAdsManager.swift | 4 ++++ ios/Video/RCTVideo.swift | 5 +++++ 2 files changed, 9 insertions(+) diff --git a/ios/Video/Features/RCTIMAAdsManager.swift b/ios/Video/Features/RCTIMAAdsManager.swift index 059ec6399e..21884857e6 100644 --- a/ios/Video/Features/RCTIMAAdsManager.swift +++ b/ios/Video/Features/RCTIMAAdsManager.swift @@ -76,6 +76,10 @@ class RCTIMAAdsManager: NSObject, IMAAdsLoaderDelegate, IMAAdsManagerDelegate { // MARK: - IMAAdsManagerDelegate func adsManager(_ adsManager: IMAAdsManager, didReceive event: IMAAdEvent) { + // Mute ad if the main player is muted + if (_video.isMuted()) { + adsManager.volume = 0; + } // Play each ad once it has been loaded if event.type == IMAAdEventType.LOADED { adsManager.start() diff --git a/ios/Video/RCTVideo.swift b/ios/Video/RCTVideo.swift index be1c0981e5..cc0df2c651 100644 --- a/ios/Video/RCTVideo.swift +++ b/ios/Video/RCTVideo.swift @@ -496,6 +496,11 @@ class RCTVideo: UIView, RCTVideoPlayerViewControllerDelegate, RCTPlayerObserverH applyModifiers() } + @objc + func isMuted() -> Bool { + return _muted + } + @objc func setMuted(_ muted:Bool) { _muted = muted From 86cd6220bed6fc0c4db048f5e89d8a41b9094b50 Mon Sep 17 00:00:00 2001 From: Francesco Benigno Date: Tue, 28 Mar 2023 13:25:10 +0200 Subject: [PATCH 016/117] update changelog --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 71b54f1c63..045d3acae1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,7 @@ ### Version 6.0.0-alpha.5 +- iOS: make sure that the audio in ads is muted when the player is muted. [#3068](https://github.com/react-native-video/react-native-video/pull/3077) - iOS: ensure controls are not displayed when disabled by user [#3017](https://github.com/react-native-video/react-native-video/pull/3017) - iOS: app crashes on call to presentFullScreenPlayer [#2808](https://github.com/react-native-video/react-native-video/pull/2971) - Android: Fix publicated progress handler causing duplicated progress event [#2972](https://github.com/react-native-video/react-native-video/pull/2972) From daabb91475dd03dbf104341c56f3daa8701c09f3 Mon Sep 17 00:00:00 2001 From: Craig Martin Date: Sun, 2 Apr 2023 14:02:56 -0400 Subject: [PATCH 017/117] Allow audio output via earpiece --- API.md | 8 + CHANGELOG.md | 2 + Video.js | 1 + .../exoplayer/ReactExoplayerView.java | 414 +++++++++++------- .../exoplayer/ReactExoplayerViewManager.java | 86 ++-- ios/Video/Features/RCTPlayerOperations.swift | 2 +- ios/Video/RCTVideo.swift | 15 + ios/Video/RCTVideoManager.m | 1 + 8 files changed, 331 insertions(+), 198 deletions(-) diff --git a/API.md b/API.md index 4078155892..629ec74c8f 100644 --- a/API.md +++ b/API.md @@ -294,6 +294,7 @@ var styles = StyleSheet.create({ | [adTagUrl](#adTagUrl) | Android, iOS | | [allowsExternalPlayback](#allowsexternalplayback) | iOS | | [audioOnly](#audioonly) | All | +| [audioOutput](#audioOutput) | Android, iOS | | [automaticallyWaitsToMinimizeStalling](#automaticallyWaitsToMinimizeStalling) | iOS | | [backBufferDurationMs](#backBufferDurationMs) | Android | | [bufferConfig](#bufferconfig) | Android | @@ -417,6 +418,13 @@ For this to work, the poster prop must be set. Platforms: all +#### audioOutput +Changes the audio output. +* **speaker (default)** - plays through speaker +* **earpiece** - plays through earpiece + +Platforms: Android, iOS + #### automaticallyWaitsToMinimizeStalling A Boolean value that indicates whether the player should automatically delay playback in order to minimize stalling. For clients linked against iOS 10.0 and later * **false** - Immediately starts playback diff --git a/CHANGELOG.md b/CHANGELOG.md index 71b54f1c63..c8f79f49b6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,7 @@ ## Changelog +- Feature: playing audio over earpiece [#2887](https://github.com/react-native-video/react-native-video/issues/2887) + ### Version 6.0.0-alpha.5 - iOS: ensure controls are not displayed when disabled by user [#3017](https://github.com/react-native-video/react-native-video/pull/3017) diff --git a/Video.js b/Video.js index ac6bc085a1..5952f3f936 100644 --- a/Video.js +++ b/Video.js @@ -520,6 +520,7 @@ Video.propTypes = { disableBuffering: PropTypes.bool, controls: PropTypes.bool, audioOnly: PropTypes.bool, + audioOutput: PropTypes.oneOf(['earpiece', 'speaker']), currentTime: PropTypes.number, fullscreenAutorotate: PropTypes.bool, fullscreenOrientation: PropTypes.oneOf(['all', 'landscape', 'portrait']), diff --git a/android/src/main/java/com/brentvatne/exoplayer/ReactExoplayerView.java b/android/src/main/java/com/brentvatne/exoplayer/ReactExoplayerView.java index 0c0fbc1747..f32d7e44db 100644 --- a/android/src/main/java/com/brentvatne/exoplayer/ReactExoplayerView.java +++ b/android/src/main/java/com/brentvatne/exoplayer/ReactExoplayerView.java @@ -47,6 +47,7 @@ import com.google.android.exoplayer2.ExoPlayer; import com.google.android.exoplayer2.Timeline; import com.google.android.exoplayer2.Tracks; +import com.google.android.exoplayer2.audio.AudioAttributes; import com.google.android.exoplayer2.drm.DefaultDrmSessionManager; import com.google.android.exoplayer2.drm.DefaultDrmSessionManagerProvider; import com.google.android.exoplayer2.drm.DrmSessionEventListener; @@ -135,6 +136,33 @@ class ReactExoplayerView extends FrameLayout implements DEFAULT_COOKIE_MANAGER.setCookiePolicy(CookiePolicy.ACCEPT_ORIGINAL_SERVER); } + @SuppressLint("InlinedApi") + public enum AudioOutput { + SPEAKER("speaker", C.STREAM_TYPE_MUSIC), + EARPIECE("earpiece", C.STREAM_TYPE_VOICE_CALL); + + private final int streamType; + private final String mName; + + AudioOutput(final String name, int stream) { + mName = name; + streamType = stream; + } + + public static AudioOutput get(String name) { + for (AudioOutput d : values()) { + if (d.mName.equalsIgnoreCase(name)) + return d; + } + return SPEAKER; + } + + @Override + public String toString() { + return getClass().getSimpleName() + "(" + this.mName + ", " + streamType + ")"; + } + } + private final VideoEventEmitter eventEmitter; private final ReactExoplayerConfig config; private final DefaultBandwidthMeter bandwidthMeter; @@ -161,6 +189,7 @@ class ReactExoplayerView extends FrameLayout implements private boolean muted = false; private boolean hasAudioFocus = false; private float rate = 1f; + private AudioOutput audioOutput = AudioOutput.SPEAKER; private float audioVolume = 1f; private int minLoadRetryCount = 3; private int maxBitRate = 0; @@ -236,7 +265,8 @@ public void handleMessage(Message msg) { lastPos = pos; lastBufferDuration = bufferedDuration; lastDuration = duration; - eventEmitter.progressChanged(pos, bufferedDuration, player.getDuration(), getPositionInFirstPeriodMsForCurrentWindow(pos)); + eventEmitter.progressChanged(pos, bufferedDuration, player.getDuration(), + getPositionInFirstPeriodMsForCurrentWindow(pos)); } msg = obtainMessage(SHOW_PROGRESS); sendMessageDelayed(msg, Math.round(mProgressUpdateInterval)); @@ -248,7 +278,7 @@ public void handleMessage(Message msg) { public double getPositionInFirstPeriodMsForCurrentWindow(long currentPosition) { Timeline.Window window = new Timeline.Window(); - if(!player.getCurrentTimeline().isEmpty()) { + if (!player.getCurrentTimeline().isEmpty()) { player.getCurrentTimeline().getWindow(player.getCurrentMediaItemIndex(), window); } return window.windowStartTimeMs + currentPosition; @@ -307,7 +337,8 @@ protected void onAttachedToWindow() { @Override protected void onDetachedFromWindow() { super.onDetachedFromWindow(); - /* We want to be able to continue playing audio when switching tabs. + /* + * We want to be able to continue playing audio when switching tabs. * Leave this here in case it causes issues. */ // stopPlayback(); @@ -341,7 +372,7 @@ public void cleanUpResources() { stopPlayback(); } - //BandwidthMeter.EventListener implementation + // BandwidthMeter.EventListener implementation @Override public void onBandwidthSample(int elapsedMs, long bytes, long bitrate) { if (mReportBandwidth) { @@ -363,7 +394,8 @@ public void onBandwidthSample(int elapsedMs, long bytes, long bitrate) { * Toggling the visibility of the player control view */ private void togglePlayerControlVisibility() { - if(player == null) return; + if (player == null) + return; reLayout(playerControlView); if (playerControlView.isVisible()) { playerControlView.hide(); @@ -381,12 +413,13 @@ private void initializePlayerControl() { } if (fullScreenPlayerView == null) { - fullScreenPlayerView = new FullScreenPlayerView(getContext(), exoPlayerView, playerControlView, new OnBackPressedCallback(true) { - @Override - public void handleOnBackPressed() { - setFullscreen(false); - } - }); + fullScreenPlayerView = new FullScreenPlayerView(getContext(), exoPlayerView, playerControlView, + new OnBackPressedCallback(true) { + @Override + public void handleOnBackPressed() { + setFullscreen(false); + } + }); } // Setting the player for the playerControlView @@ -403,7 +436,7 @@ public void onClick(View v) { } }); - //Handling the playButton click event + // Handling the playButton click event ImageButton playButton = playerControlView.findViewById(R.id.exo_play); playButton.setOnClickListener(new View.OnClickListener() { @Override @@ -415,7 +448,7 @@ public void onClick(View v) { } }); - //Handling the pauseButton click event + // Handling the pauseButton click event ImageButton pauseButton = playerControlView.findViewById(R.id.exo_pause); pauseButton.setOnClickListener(new View.OnClickListener() { @Override @@ -424,7 +457,7 @@ public void onClick(View v) { } }); - //Handling the fullScreenButton click event + // Handling the fullScreenButton click event final ImageButton fullScreenButton = playerControlView.findViewById(R.id.exo_fullscreen); fullScreenButton.setOnClickListener(v -> setFullscreen(!isFullscreen)); updateFullScreenButtonVisbility(); @@ -442,14 +475,16 @@ public void onPlaybackStateChanged(int playbackState) { pauseButton.setVisibility(INVISIBLE); } reLayout(playPauseControlContainer); - //Remove this eventListener once its executed. since UI will work fine once after the reLayout is done + // Remove this eventListener once its executed. since UI will work fine once + // after the reLayout is done player.removeListener(eventListener); } @Override public void onPlayWhenReadyChanged(boolean playWhenReady, int reason) { reLayout(playPauseControlContainer); - //Remove this eventListener once its executed. since UI will work fine once after the reLayout is done + // Remove this eventListener once its executed. since UI will work fine once + // after the reLayout is done player.removeListener(eventListener); } }; @@ -460,7 +495,8 @@ public void onPlayWhenReadyChanged(boolean playWhenReady, int reason) { * Adding Player control to the frame layout */ private void addPlayerControl() { - if(playerControlView == null) return; + if (playerControlView == null) + return; LayoutParams layoutParams = new LayoutParams( LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT); @@ -475,12 +511,15 @@ private void addPlayerControl() { /** * Update the layout - * @param view view needs to update layout + * + * @param view view needs to update layout * - * This is a workaround for the open bug in react-native: https://github.com/facebook/react-native/issues/17968 + * This is a workaround for the open bug in react-native: + * https://github.com/facebook/react-native/issues/17968 */ private void reLayout(View view) { - if (view == null) return; + if (view == null) + return; view.measure(MeasureSpec.makeMeasureSpec(getMeasuredWidth(), MeasureSpec.EXACTLY), MeasureSpec.makeMeasureSpec(getMeasuredHeight(), MeasureSpec.EXACTLY)); view.layout(view.getLeft(), view.getTop(), view.getMeasuredWidth(), view.getMeasuredHeight()); @@ -489,7 +528,10 @@ private void reLayout(View view) { private class RNVLoadControl extends DefaultLoadControl { private int availableHeapInBytes = 0; private Runtime runtime; - public RNVLoadControl(DefaultAllocator allocator, int minBufferMs, int maxBufferMs, int bufferForPlaybackMs, int bufferForPlaybackAfterRebufferMs, int targetBufferBytes, boolean prioritizeTimeOverSizeThresholds, int backBufferDurationMs, boolean retainBackBufferFromKeyframe) { + + public RNVLoadControl(DefaultAllocator allocator, int minBufferMs, int maxBufferMs, int bufferForPlaybackMs, + int bufferForPlaybackAfterRebufferMs, int targetBufferBytes, boolean prioritizeTimeOverSizeThresholds, + int backBufferDurationMs, boolean retainBackBufferFromKeyframe) { super(allocator, minBufferMs, maxBufferMs, @@ -500,8 +542,10 @@ public RNVLoadControl(DefaultAllocator allocator, int minBufferMs, int maxBuffer backBufferDurationMs, retainBackBufferFromKeyframe); runtime = Runtime.getRuntime(); - ActivityManager activityManager = (ActivityManager) themedReactContext.getSystemService(themedReactContext.ACTIVITY_SERVICE); - availableHeapInBytes = (int) Math.floor(activityManager.getMemoryClass() * maxHeapAllocationPercent * 1024 * 1024); + ActivityManager activityManager = (ActivityManager) themedReactContext + .getSystemService(themedReactContext.ACTIVITY_SERVICE); + availableHeapInBytes = (int) Math + .floor(activityManager.getMemoryClass() * maxHeapAllocationPercent * 1024 * 1024); } @Override @@ -516,10 +560,11 @@ public boolean shouldContinueLoading(long playbackPositionUs, long bufferedDurat } long usedMemory = runtime.totalMemory() - runtime.freeMemory(); long freeMemory = runtime.maxMemory() - usedMemory; - long reserveMemory = (long)minBufferMemoryReservePercent * runtime.maxMemory(); - long bufferedMs = bufferedDurationUs / (long)1000; + long reserveMemory = (long) minBufferMemoryReservePercent * runtime.maxMemory(); + long bufferedMs = bufferedDurationUs / (long) 1000; if (reserveMemory > freeMemory && bufferedMs > 2000) { - // We don't have enough memory in reserve so we stop buffering to allow other components to use it instead + // We don't have enough memory in reserve so we stop buffering to allow other + // components to use it instead return false; } if (runtime.freeMemory() == 0) { @@ -552,7 +597,8 @@ public void run() { } if (playerNeedsSource && srcUri != null) { exoPlayerView.invalidateAspectRatio(); - // DRM session manager creation must be done on a different thread to prevent crashes so we start a new thread + // DRM session manager creation must be done on a different thread to prevent + // crashes so we start a new thread ExecutorService es = Executors.newSingleThreadExecutor(); es.execute(new Runnable() { @Override @@ -562,13 +608,15 @@ public void run() { if (drmSessionManager == null && self.drmUUID != null) { // Failed to intialize DRM session manager - cannot continue Log.e("ExoPlayer Exception", "Failed to initialize DRM Session Manager Framework!"); - eventEmitter.error("Failed to initialize DRM Session Manager Framework!", new Exception("DRM Session Manager Framework failure!"), "3003"); + eventEmitter.error("Failed to initialize DRM Session Manager Framework!", + new Exception("DRM Session Manager Framework failure!"), "3003"); return; } - + if (activity == null) { Log.e("ExoPlayer Exception", "Failed to initialize Player!"); - eventEmitter.error("Failed to initialize Player!", new Exception("Current Activity is null!"), "1001"); + eventEmitter.error("Failed to initialize Player!", + new Exception("Current Activity is null!"), "1001"); return; } @@ -618,24 +666,22 @@ private void initializePlayerCore(ReactExoplayerView self) { -1, true, backBufferDurationMs, - DefaultLoadControl.DEFAULT_RETAIN_BACK_BUFFER_FROM_KEYFRAME - ); - DefaultRenderersFactory renderersFactory = - new DefaultRenderersFactory(getContext()) - .setExtensionRendererMode(DefaultRenderersFactory.EXTENSION_RENDERER_MODE_OFF); + DefaultLoadControl.DEFAULT_RETAIN_BACK_BUFFER_FROM_KEYFRAME); + DefaultRenderersFactory renderersFactory = new DefaultRenderersFactory(getContext()) + .setExtensionRendererMode(DefaultRenderersFactory.EXTENSION_RENDERER_MODE_OFF); // Create an AdsLoader. adsLoader = new ImaAdsLoader.Builder(themedReactContext).setAdEventListener(this).build(); MediaSource.Factory mediaSourceFactory = new DefaultMediaSourceFactory(mediaDataSourceFactory) - .setLocalAdInsertionComponents(unusedAdTagUri -> adsLoader, exoPlayerView); + .setLocalAdInsertionComponents(unusedAdTagUri -> adsLoader, exoPlayerView); player = new ExoPlayer.Builder(getContext(), renderersFactory) - .setTrackSelector(self.trackSelector) - .setBandwidthMeter(bandwidthMeter) - .setLoadControl(loadControl) - .setMediaSourceFactory(mediaSourceFactory) - .build(); + .setTrackSelector(self.trackSelector) + .setBandwidthMeter(bandwidthMeter) + .setLoadControl(loadControl) + .setMediaSourceFactory(mediaSourceFactory) + .build(); player.addListener(self); exoPlayerView.setPlayer(player); if (adsLoader != null) { @@ -659,7 +705,8 @@ private DrmSessionManager initializePlayerDrm(ReactExoplayerView self) { } catch (UnsupportedDrmException e) { int errorStringId = Util.SDK_INT < 18 ? R.string.error_drm_not_supported : (e.reason == UnsupportedDrmException.REASON_UNSUPPORTED_SCHEME - ? R.string.error_drm_unsupported_scheme : R.string.error_drm_unknown); + ? R.string.error_drm_unsupported_scheme + : R.string.error_drm_unknown); eventEmitter.error(getResources().getString(errorStringId), e, "3003"); return null; } @@ -675,7 +722,8 @@ private void initializePlayerSource(ReactExoplayerView self, DrmSessionManager d MediaSource.Factory mediaSourceFactory = new DefaultMediaSourceFactory(mediaDataSourceFactory) .setLocalAdInsertionComponents(unusedAdTagUri -> adsLoader, exoPlayerView); DataSpec adTagDataSpec = new DataSpec(adTagUrl); - mediaSourceWithAds = new AdsMediaSource(videoSource, adTagDataSpec, ImmutableList.of(srcUri, adTagUrl), mediaSourceFactory, adsLoader, exoPlayerView); + mediaSourceWithAds = new AdsMediaSource(videoSource, adTagDataSpec, ImmutableList.of(srcUri, adTagUrl), + mediaSourceFactory, adsLoader, exoPlayerView); } MediaSource mediaSource; if (mediaSourceList.size() == 0) { @@ -691,8 +739,7 @@ private void initializePlayerSource(ReactExoplayerView self, DrmSessionManager d mediaSourceList.add(0, videoSource); } MediaSource[] textSourceArray = mediaSourceList.toArray( - new MediaSource[mediaSourceList.size()] - ); + new MediaSource[mediaSourceList.size()]); mediaSource = new MergingMediaSource(textSourceArray); } @@ -728,11 +775,13 @@ private void finishPlayerInitialization() { startBufferCheckTimer(); } - private DrmSessionManager buildDrmSessionManager(UUID uuid, String licenseUrl, String[] keyRequestPropertiesArray) throws UnsupportedDrmException { + private DrmSessionManager buildDrmSessionManager(UUID uuid, String licenseUrl, String[] keyRequestPropertiesArray) + throws UnsupportedDrmException { return buildDrmSessionManager(uuid, licenseUrl, keyRequestPropertiesArray, 0); } - private DrmSessionManager buildDrmSessionManager(UUID uuid, String licenseUrl, String[] keyRequestPropertiesArray, int retryCount) throws UnsupportedDrmException { + private DrmSessionManager buildDrmSessionManager(UUID uuid, String licenseUrl, String[] keyRequestPropertiesArray, + int retryCount) throws UnsupportedDrmException { if (Util.SDK_INT < 18) { return null; } @@ -750,12 +799,13 @@ private DrmSessionManager buildDrmSessionManager(UUID uuid, String licenseUrl, S mediaDrm.setPropertyString("securityLevel", "L3"); } return new DefaultDrmSessionManager(uuid, mediaDrm, drmCallback, null, false, 3); - } catch(UnsupportedDrmException ex) { + } catch (UnsupportedDrmException ex) { // Unsupported DRM exceptions are handled by the calling method throw ex; } catch (Exception ex) { if (retryCount < 3) { - // Attempt retry 3 times in case where the OS Media DRM Framework fails for whatever reason + // Attempt retry 3 times in case where the OS Media DRM Framework fails for + // whatever reason return buildDrmSessionManager(uuid, licenseUrl, keyRequestPropertiesArray, ++retryCount); } // Handle the unknow exception and emit to JS @@ -776,8 +826,7 @@ private MediaSource buildMediaSource(Uri uri, String overrideExtension, DrmSessi if (adTagUrl != null) { mediaItemBuilder.setAdsConfiguration( - new MediaItem.AdsConfiguration.Builder(adTagUrl).build() - ); + new MediaItem.AdsConfiguration.Builder(adTagUrl).build()); } MediaItem mediaItem = mediaItemBuilder.build(); @@ -797,33 +846,29 @@ public DrmSessionManager get(MediaItem mediaItem) { case CONTENT_TYPE_SS: return new SsMediaSource.Factory( new DefaultSsChunkSource.Factory(mediaDataSourceFactory), - buildDataSourceFactory(false) - ).setDrmSessionManagerProvider(drmProvider) - .setLoadErrorHandlingPolicy( - config.buildLoadErrorHandlingPolicy(minLoadRetryCount) - ).createMediaSource(mediaItem); + buildDataSourceFactory(false)).setDrmSessionManagerProvider(drmProvider) + .setLoadErrorHandlingPolicy( + config.buildLoadErrorHandlingPolicy(minLoadRetryCount)) + .createMediaSource(mediaItem); case CONTENT_TYPE_DASH: return new DashMediaSource.Factory( new DefaultDashChunkSource.Factory(mediaDataSourceFactory), - buildDataSourceFactory(false) - ).setDrmSessionManagerProvider(drmProvider) - .setLoadErrorHandlingPolicy( - config.buildLoadErrorHandlingPolicy(minLoadRetryCount) - ).createMediaSource(mediaItem); + buildDataSourceFactory(false)).setDrmSessionManagerProvider(drmProvider) + .setLoadErrorHandlingPolicy( + config.buildLoadErrorHandlingPolicy(minLoadRetryCount)) + .createMediaSource(mediaItem); case CONTENT_TYPE_HLS: return new HlsMediaSource.Factory( - mediaDataSourceFactory - ).setDrmSessionManagerProvider(drmProvider) - .setLoadErrorHandlingPolicy( - config.buildLoadErrorHandlingPolicy(minLoadRetryCount) - ).createMediaSource(mediaItem); + mediaDataSourceFactory).setDrmSessionManagerProvider(drmProvider) + .setLoadErrorHandlingPolicy( + config.buildLoadErrorHandlingPolicy(minLoadRetryCount)) + .createMediaSource(mediaItem); case CONTENT_TYPE_OTHER: return new ProgressiveMediaSource.Factory( - mediaDataSourceFactory - ).setDrmSessionManagerProvider(drmProvider) - .setLoadErrorHandlingPolicy( - config.buildLoadErrorHandlingPolicy(minLoadRetryCount) - ).createMediaSource(mediaItem); + mediaDataSourceFactory).setDrmSessionManagerProvider(drmProvider) + .setLoadErrorHandlingPolicy( + config.buildLoadErrorHandlingPolicy(minLoadRetryCount)) + .createMediaSource(mediaItem); default: { throw new IllegalStateException("Unsupported type: " + type); } @@ -840,7 +885,8 @@ private ArrayList buildTextSources() { ReadableMap textTrack = textTracks.getMap(i); String language = textTrack.getString("language"); String title = textTrack.hasKey("title") - ? textTrack.getString("title") : language + " " + i; + ? textTrack.getString("title") + : language + " " + i; Uri uri = Uri.parse(textTrack.getString("uri")); MediaSource textSource = buildTextSource(title, uri, textTrack.getString("type"), language); @@ -971,7 +1017,8 @@ private void clearResumePosition() { /** * Returns a new DataSource factory. * - * @param useBandwidthMeter Whether to set {@link #bandwidthMeter} as a listener to the new + * @param useBandwidthMeter Whether to set {@link #bandwidthMeter} as a listener + * to the new * DataSource factory. * @return A new DataSource factory. */ @@ -983,15 +1030,16 @@ private DataSource.Factory buildDataSourceFactory(boolean useBandwidthMeter) { /** * Returns a new HttpDataSource factory. * - * @param useBandwidthMeter Whether to set {@link #bandwidthMeter} as a listener to the new - * DataSource factory. + * @param useBandwidthMeter Whether to set {@link #bandwidthMeter} as a listener + * to the new + * DataSource factory. * @return A new HttpDataSource factory. */ private HttpDataSource.Factory buildHttpDataSourceFactory(boolean useBandwidthMeter) { - return DataSourceUtil.getDefaultHttpDataSourceFactory(this.themedReactContext, useBandwidthMeter ? bandwidthMeter : null, requestHeaders); + return DataSourceUtil.getDefaultHttpDataSourceFactory(this.themedReactContext, + useBandwidthMeter ? bandwidthMeter : null, requestHeaders); } - // AudioManager.OnAudioFocusChangeListener implementation @Override @@ -1045,7 +1093,8 @@ public void onIsLoadingChanged(boolean isLoading) { @Override public void onEvents(Player player, Player.Events events) { - if (events.contains(Player.EVENT_PLAYBACK_STATE_CHANGED) || events.contains(Player.EVENT_PLAY_WHEN_READY_CHANGED)) { + if (events.contains(Player.EVENT_PLAYBACK_STATE_CHANGED) + || events.contains(Player.EVENT_PLAY_WHEN_READY_CHANGED)) { int playbackState = player.getPlaybackState(); boolean playWhenReady = player.getPlayWhenReady(); String text = "onStateChanged: playWhenReady=" + playWhenReady + ", playbackState="; @@ -1059,38 +1108,38 @@ public void onEvents(Player player, Player.Events events) { setKeepScreenOn(false); } break; - case Player.STATE_BUFFERING: - text += "buffering"; - onBuffering(true); - clearProgressMessageHandler(); - setKeepScreenOn(preventsDisplaySleepDuringVideoPlayback); - break; - case Player.STATE_READY: - text += "ready"; - eventEmitter.ready(); - onBuffering(false); - clearProgressMessageHandler(); // ensure there is no other message - startProgressHandler(); - videoLoaded(); - if (selectTrackWhenReady && isUsingContentResolution) { - selectTrackWhenReady = false; - setSelectedTrack(C.TRACK_TYPE_VIDEO, videoTrackType, videoTrackValue); - } - // Setting the visibility for the playerControlView - if (playerControlView != null) { - playerControlView.show(); - } - setKeepScreenOn(preventsDisplaySleepDuringVideoPlayback); - break; - case Player.STATE_ENDED: - text += "ended"; - eventEmitter.end(); - onStopPlayback(); - setKeepScreenOn(false); - break; - default: - text += "unknown"; - break; + case Player.STATE_BUFFERING: + text += "buffering"; + onBuffering(true); + clearProgressMessageHandler(); + setKeepScreenOn(preventsDisplaySleepDuringVideoPlayback); + break; + case Player.STATE_READY: + text += "ready"; + eventEmitter.ready(); + onBuffering(false); + clearProgressMessageHandler(); // ensure there is no other message + startProgressHandler(); + videoLoaded(); + if (selectTrackWhenReady && isUsingContentResolution) { + selectTrackWhenReady = false; + setSelectedTrack(C.TRACK_TYPE_VIDEO, videoTrackType, videoTrackValue); + } + // Setting the visibility for the playerControlView + if (playerControlView != null) { + playerControlView.show(); + } + setKeepScreenOn(preventsDisplaySleepDuringVideoPlayback); + break; + case Player.STATE_ENDED: + text += "ended"; + eventEmitter.end(); + onStopPlayback(); + setKeepScreenOn(false); + break; + default: + text += "unknown"; + break; } } } @@ -1100,12 +1149,14 @@ private void startProgressHandler() { } /* - The progress message handler will duplicate recursions of the onProgressMessage handler - on change of player state from any state to STATE_READY with playWhenReady is true (when - the video is not paused). This clears all existing messages. + * The progress message handler will duplicate recursions of the + * onProgressMessage handler + * on change of player state from any state to STATE_READY with playWhenReady is + * true (when + * the video is not paused). This clears all existing messages. */ private void clearProgressMessageHandler() { - progressHandler.removeMessages(SHOW_PROGRESS); + progressHandler.removeMessages(SHOW_PROGRESS); } private void videoLoaded() { @@ -1129,20 +1180,21 @@ private void videoLoaded() { long duration = player.getDuration(); long currentPosition = player.getCurrentPosition(); ArrayList audioTracks = getAudioTrackInfo(); - ArrayList textTracks = getTextTrackInfo(); + ArrayList textTracks = getTextTrackInfo(); if (this.contentStartTime != -1L) { ExecutorService es = Executors.newSingleThreadExecutor(); es.execute(new Runnable() { @Override public void run() { - // To prevent ANRs caused by getVideoTrackInfo we run this on a different thread and notify the player only when we're done + // To prevent ANRs caused by getVideoTrackInfo we run this on a different thread + // and notify the player only when we're done ArrayList videoTracks = getVideoTrackInfoFromManifest(); if (videoTracks != null) { isUsingContentResolution = true; } eventEmitter.load(duration, currentPosition, width, height, - audioTracks, textTracks, videoTracks, trackId ); + audioTracks, textTracks, videoTracks, trackId); } }); @@ -1157,9 +1209,9 @@ public void run() { } private static boolean isTrackSelected(TrackSelection selection, TrackGroup group, - int trackIndex){ + int trackIndex) { return selection != null && selection.getTrackGroup() == group - && selection.indexOf( trackIndex ) != C.INDEX_UNSET; + && selection.indexOf(trackIndex) != C.INDEX_UNSET; } private ArrayList getAudioTrackInfo() { @@ -1176,7 +1228,7 @@ private ArrayList getAudioTrackInfo() { } TrackGroupArray groups = info.getTrackGroups(index); TrackSelectionArray selectionArray = player.getCurrentTrackSelections(); - TrackSelection selection = selectionArray.get( C.TRACK_TYPE_AUDIO ); + TrackSelection selection = selectionArray.get(C.TRACK_TYPE_AUDIO); for (int i = 0; i < groups.length; ++i) { TrackGroup group = groups.get(i); @@ -1187,7 +1239,7 @@ private ArrayList getAudioTrackInfo() { audioTrack.m_mimeType = format.sampleMimeType; audioTrack.m_language = format.language != null ? format.language : ""; audioTrack.m_bitrate = format.bitrate == Format.NO_VALUE ? 0 : format.bitrate; - audioTrack.m_isSelected = isTrackSelected(selection, group, 0 ); + audioTrack.m_isSelected = isTrackSelected(selection, group, 0); audioTracks.add(audioTrack); } return audioTracks; @@ -1229,7 +1281,8 @@ private ArrayList getVideoTrackInfoFromManifest() { return this.getVideoTrackInfoFromManifest(0); } - // We need retry count to in case where minefest request fails from poor network conditions + // We need retry count to in case where minefest request fails from poor network + // conditions @WorkerThread private ArrayList getVideoTrackInfoFromManifest(int retryCount) { ExecutorService es = Executors.newSingleThreadExecutor(); @@ -1244,18 +1297,20 @@ private ArrayList getVideoTrackInfoFromManifest(int retryCount) { public ArrayList call() throws Exception { ArrayList videoTracks = new ArrayList<>(); - try { + try { DashManifest manifest = DashUtil.loadManifest(this.ds, this.uri); int periodCount = manifest.getPeriodCount(); for (int i = 0; i < periodCount; i++) { Period period = manifest.getPeriod(i); - for (int adaptationIndex = 0; adaptationIndex < period.adaptationSets.size(); adaptationIndex++) { + for (int adaptationIndex = 0; adaptationIndex < period.adaptationSets + .size(); adaptationIndex++) { AdaptationSet adaptation = period.adaptationSets.get(adaptationIndex); if (adaptation.type != C.TRACK_TYPE_VIDEO) { continue; } boolean hasFoundContentPeriod = false; - for (int representationIndex = 0; representationIndex < adaptation.representations.size(); representationIndex++) { + for (int representationIndex = 0; representationIndex < adaptation.representations + .size(); representationIndex++) { Representation representation = adaptation.representations.get(representationIndex); Format format = representation.format; if (isFormatSupported(format)) { @@ -1268,7 +1323,8 @@ public ArrayList call() throws Exception { videoTrack.m_height = format.height == Format.NO_VALUE ? 0 : format.height; videoTrack.m_bitrate = format.bitrate == Format.NO_VALUE ? 0 : format.bitrate; videoTrack.m_codecs = format.codecs != null ? format.codecs : ""; - videoTrack.m_trackId = format.id == null ? String.valueOf(representationIndex) : format.id; + videoTrack.m_trackId = format.id == null ? String.valueOf(representationIndex) + : format.id; videoTracks.add(videoTrack); } } @@ -1277,7 +1333,8 @@ public ArrayList call() throws Exception { } } } - } catch (Exception e) {} + } catch (Exception e) { + } return null; } }); @@ -1289,7 +1346,8 @@ public ArrayList call() throws Exception { } es.shutdown(); return results; - } catch (Exception e) {} + } catch (Exception e) { + } return null; } @@ -1305,7 +1363,7 @@ private ArrayList getTextTrackInfo() { return textTracks; } TrackSelectionArray selectionArray = player.getCurrentTrackSelections(); - TrackSelection selection = selectionArray.get( C.TRACK_TYPE_VIDEO ); + TrackSelection selection = selectionArray.get(C.TRACK_TYPE_VIDEO); TrackGroupArray groups = info.getTrackGroups(index); for (int i = 0; i < groups.length; ++i) { @@ -1317,7 +1375,7 @@ private ArrayList getTextTrackInfo() { textTrack.m_title = format.id != null ? format.id : ""; textTrack.m_mimeType = format.sampleMimeType; textTrack.m_language = format.language != null ? format.language : ""; - textTrack.m_isSelected = isTrackSelected(selection, group, 0 ); + textTrack.m_isSelected = isTrackSelected(selection, group, 0); textTracks.add(textTrack); } return textTracks; @@ -1339,17 +1397,21 @@ private void onBuffering(boolean buffering) { @Override public void onPositionDiscontinuity(Player.PositionInfo oldPosition, Player.PositionInfo newPosition, int reason) { if (playerNeedsSource) { - // This will only occur if the user has performed a seek whilst in the error state. Update the - // resume position so that if the user then retries, playback will resume from the position to + // This will only occur if the user has performed a seek whilst in the error + // state. Update the + // resume position so that if the user then retries, playback will resume from + // the position to // which they seeked. updateResumePosition(); } if (isUsingContentResolution) { - // Discontinuity events might have a different track list so we update the selected track + // Discontinuity events might have a different track list so we update the + // selected track setSelectedTrack(C.TRACK_TYPE_VIDEO, videoTrackType, videoTrackValue); selectTrackWhenReady = true; } - // When repeat is turned on, reaching the end of the video will not cause a state change + // When repeat is turned on, reaching the end of the video will not cause a + // state change // so we need to explicitly detect it. if (reason == Player.DISCONTINUITY_REASON_AUTO_TRANSITION && player.getRepeatMode() == Player.REPEAT_MODE_ONE) { @@ -1369,7 +1431,8 @@ public void onPlaybackStateChanged(int playbackState) { eventEmitter.seek(player.getCurrentPosition(), seekTime); seekTime = C.TIME_UNSET; if (isUsingContentResolution) { - // We need to update the selected track to make sure that it still matches user selection if track list has changed in this period + // We need to update the selected track to make sure that it still matches user + // selection if track list has changed in this period setSelectedTrack(C.TRACK_TYPE_VIDEO, videoTrackType, videoTrackValue); } } @@ -1410,14 +1473,15 @@ public void onPlayerError(PlaybackException e) { String errorString = "ExoPlaybackException: " + PlaybackException.getErrorCodeName(e.errorCode); String errorCode = "2" + String.valueOf(e.errorCode); boolean needsReInitialization = false; - switch(e.errorCode) { + switch (e.errorCode) { case PlaybackException.ERROR_CODE_DRM_DEVICE_REVOKED: case PlaybackException.ERROR_CODE_DRM_LICENSE_ACQUISITION_FAILED: case PlaybackException.ERROR_CODE_DRM_PROVISIONING_FAILED: case PlaybackException.ERROR_CODE_DRM_SYSTEM_ERROR: case PlaybackException.ERROR_CODE_DRM_UNSPECIFIED: if (!hasDrmFailed) { - // When DRM fails to reach the app level certificate server it will fail with a source error so we assume that it is DRM related and try one more time + // When DRM fails to reach the app level certificate server it will fail with a + // source error so we assume that it is DRM related and try one more time hasDrmFailed = true; playerNeedsSource = true; updateResumePosition(); @@ -1472,9 +1536,9 @@ public void setSrc(final Uri uri, final String extension, Map he this.srcUri = uri; this.extension = extension; this.requestHeaders = headers; - this.mediaDataSourceFactory = - DataSourceUtil.getDefaultDataSourceFactory(this.themedReactContext, bandwidthMeter, - this.requestHeaders); + this.mediaDataSourceFactory = DataSourceUtil.getDefaultDataSourceFactory(this.themedReactContext, + bandwidthMeter, + this.requestHeaders); if (!isSourceEqual) { reloadSource(); @@ -1562,7 +1626,8 @@ public void disableTrack(int rendererIndex) { } public void setSelectedTrack(int trackType, String type, Dynamic value) { - if (player == null) return; + if (player == null) + return; int rendererIndex = getTrackRendererIndex(trackType); if (rendererIndex == C.INDEX_UNSET) { return; @@ -1621,20 +1686,23 @@ public void setSelectedTrack(int trackType, String type, Dynamic value) { usingExactMatch = true; break; } else if (isUsingContentResolution) { - // When using content resolution rather than ads, we need to try and find the closest match if there is no exact match + // When using content resolution rather than ads, we need to try and find the + // closest match if there is no exact match if (closestFormat != null) { - if ((format.bitrate > closestFormat.bitrate || format.height > closestFormat.height) && format.height < height) { + if ((format.bitrate > closestFormat.bitrate || format.height > closestFormat.height) + && format.height < height) { // Higher quality match closestFormat = format; closestTrackIndex = j; } - } else if(format.height < height) { + } else if (format.height < height) { closestFormat = format; closestTrackIndex = j; } } } - // This is a fallback if the new period contains only higher resolutions than the user has selected + // This is a fallback if the new period contains only higher resolutions than + // the user has selected if (closestFormat == null && isUsingContentResolution && !usingExactMatch) { // No close match found - so we pick the lowest quality int minHeight = Integer.MAX_VALUE; @@ -1656,8 +1724,8 @@ public void setSelectedTrack(int trackType, String type, Dynamic value) { } } else if (trackType == C.TRACK_TYPE_TEXT && Util.SDK_INT > 18) { // Text default // Use system settings if possible - CaptioningManager captioningManager - = (CaptioningManager)themedReactContext.getSystemService(Context.CAPTIONING_SERVICE); + CaptioningManager captioningManager = (CaptioningManager) themedReactContext + .getSystemService(Context.CAPTIONING_SERVICE); if (captioningManager != null && captioningManager.isEnabled()) { groupIndex = getGroupIndexForDefaultLocale(groups); } @@ -1688,7 +1756,7 @@ public void setSelectedTrack(int trackType, String type, Dynamic value) { // With only one tracks we can't remove any tracks so attempt to play it anyway tracks = allTracks; } else { - tracks = new ArrayList<>(supportedFormatLength + 1); + tracks = new ArrayList<>(supportedFormatLength + 1); for (int k = 0; k < allTracks.size(); k++) { Format format = group.getFormat(k); if (isFormatSupported(format)) { @@ -1707,11 +1775,11 @@ public void setSelectedTrack(int trackType, String type, Dynamic value) { TrackSelectionOverride selectionOverride = new TrackSelectionOverride(groups.get(groupIndex), tracks); DefaultTrackSelector.Parameters selectionParameters = trackSelector.getParameters() - .buildUpon() - .setRendererDisabled(rendererIndex, false) - .clearOverridesOfType(selectionOverride.getType()) - .addOverride(selectionOverride) - .build(); + .buildUpon() + .setRendererDisabled(rendererIndex, false) + .clearOverridesOfType(selectionOverride.getType()) + .addOverride(selectionOverride) + .build(); trackSelector.setParameters(selectionParameters); } @@ -1735,7 +1803,7 @@ private boolean isFormatSupported(Format format) { } private int getGroupIndexForDefaultLocale(TrackGroupArray groups) { - if (groups.length == 0){ + if (groups.length == 0) { return C.INDEX_UNSET; } @@ -1789,6 +1857,21 @@ public void setMutedModifier(boolean muted) { } } + public void setAudioOutput(AudioOutput output) { + if (audioOutput != output && player != null) { + this.audioOutput = output; + int usage = Util.getAudioUsageForStreamType(audioOutput.streamType); + int contentType = Util.getAudioContentTypeForStreamType(audioOutput.streamType); + AudioAttributes audioAttributes = new AudioAttributes.Builder().setUsage(usage).setContentType(contentType) + .build(); + player.setAudioAttributes(audioAttributes, false); + AudioManager audioManager = (AudioManager) themedReactContext.getSystemService(Context.AUDIO_SERVICE); + audioManager.setMode( + audioOutput == AudioOutput.SPEAKER ? AudioManager.MODE_NORMAL : AudioManager.MODE_IN_COMMUNICATION); + audioManager.setSpeakerphoneOn(audioOutput == AudioOutput.SPEAKER); + } + } + public void setVolumeModifier(float volume) { audioVolume = volume; if (player != null) { @@ -1843,10 +1926,11 @@ public void setBackBufferDurationMs(int backBufferDurationMs) { Runtime runtime = Runtime.getRuntime(); long usedMemory = runtime.totalMemory() - runtime.freeMemory(); long freeMemory = runtime.maxMemory() - usedMemory; - long reserveMemory = (long)minBackBufferMemoryReservePercent * runtime.maxMemory(); + long reserveMemory = (long) minBackBufferMemoryReservePercent * runtime.maxMemory(); if (reserveMemory > freeMemory) { // We don't have enough memory in reserve so we will - Log.w("ExoPlayer Warning", "Not enough reserve memory, setting back buffer to 0ms to reduce memory pressure!"); + Log.w("ExoPlayer Warning", + "Not enough reserve memory, setting back buffer to 0ms to reduce memory pressure!"); this.backBufferDurationMs = 0; return; } @@ -1854,7 +1938,7 @@ public void setBackBufferDurationMs(int backBufferDurationMs) { } public void setContentStartTime(int contentStartTime) { - this.contentStartTime = (long)contentStartTime; + this.contentStartTime = (long) contentStartTime; } public void setDisableBuffering(boolean disableBuffering) { @@ -1865,7 +1949,7 @@ private void updateFullScreenButtonVisbility() { if (playerControlView != null) { final ImageButton fullScreenButton = playerControlView.findViewById(R.id.exo_fullscreen); if (controls) { - //Handling the fullScreenButton click event + // Handling the fullScreenButton click event if (isFullscreen && fullScreenPlayerView != null && !fullScreenPlayerView.isShowing()) { fullScreenButton.setVisibility(GONE); } else { @@ -1924,7 +2008,8 @@ public void setFullscreen(boolean fullscreen) { eventEmitter.fullscreenDidDismiss(); }); } - // need to be done at the end to avoid hiding fullscreen control button when fullScreenPlayerView is shown + // need to be done at the end to avoid hiding fullscreen control button when + // fullScreenPlayerView is shown updateFullScreenButtonVisbility(); } @@ -1941,7 +2026,9 @@ public void setHideShutterView(boolean hideShutterView) { exoPlayerView.setHideShutterView(hideShutterView); } - public void setBufferConfig(int newMinBufferMs, int newMaxBufferMs, int newBufferForPlaybackMs, int newBufferForPlaybackAfterRebufferMs, double newMaxHeapAllocationPercent, double newMinBackBufferMemoryReservePercent, double newMinBufferMemoryReservePercent) { + public void setBufferConfig(int newMinBufferMs, int newMaxBufferMs, int newBufferForPlaybackMs, + int newBufferForPlaybackAfterRebufferMs, double newMaxHeapAllocationPercent, + double newMinBackBufferMemoryReservePercent, double newMinBufferMemoryReservePercent) { minBufferMs = newMinBufferMs; maxBufferMs = newMaxBufferMs; bufferForPlaybackMs = newBufferForPlaybackMs; @@ -1957,15 +2044,14 @@ public void setDrmType(UUID drmType) { this.drmUUID = drmType; } - public void setDrmLicenseUrl(String licenseUrl){ + public void setDrmLicenseUrl(String licenseUrl) { this.drmLicenseUrl = licenseUrl; } - public void setDrmLicenseHeader(String[] header){ + public void setDrmLicenseHeader(String[] header) { this.drmLicenseHeader = header; } - @Override public void onDrmKeysLoaded(int windowIndex, MediaSource.MediaPeriodId mediaPeriodId) { Log.d("DRM Info", "onDrmKeysLoaded"); @@ -1990,7 +2076,7 @@ public void onDrmKeysRemoved(int windowIndex, MediaSource.MediaPeriodId mediaPer /** * Handling controls prop * - * @param controls Controls prop, if true enable controls, if false disable them + * @param controls Controls prop, if true enable controls, if false disable them */ public void setControls(boolean controls) { this.controls = controls; diff --git a/android/src/main/java/com/brentvatne/exoplayer/ReactExoplayerViewManager.java b/android/src/main/java/com/brentvatne/exoplayer/ReactExoplayerViewManager.java index 8da025e74e..831db44c1c 100644 --- a/android/src/main/java/com/brentvatne/exoplayer/ReactExoplayerViewManager.java +++ b/android/src/main/java/com/brentvatne/exoplayer/ReactExoplayerViewManager.java @@ -49,6 +49,7 @@ public class ReactExoplayerViewManager extends ViewGroupManager 0) { Uri srcUri = RawResourceDataSource.buildRawResourceUri(identifier); @@ -201,7 +199,6 @@ public void setAdTagUrl(final ReactExoplayerView videoView, final String uriStri videoView.setAdTagUrl(adTagUrl); } - @ReactProp(name = PROP_RESIZE_MODE) public void setResizeMode(final ReactExoplayerView videoView, final String resizeModeOrdinalString) { videoView.setResizeModeModifier(convertToIntDef(resizeModeOrdinalString)); @@ -213,55 +210,62 @@ public void setRepeat(final ReactExoplayerView videoView, final boolean repeat) } @ReactProp(name = PROP_PREVENTS_DISPLAY_SLEEP_DURING_VIDEO_PLAYBACK, defaultBoolean = false) - public void setPreventsDisplaySleepDuringVideoPlayback(final ReactExoplayerView videoView, final boolean preventsSleep) { + public void setPreventsDisplaySleepDuringVideoPlayback(final ReactExoplayerView videoView, + final boolean preventsSleep) { videoView.setPreventsDisplaySleepDuringVideoPlayback(preventsSleep); } @ReactProp(name = PROP_SELECTED_VIDEO_TRACK) public void setSelectedVideoTrack(final ReactExoplayerView videoView, - @Nullable ReadableMap selectedVideoTrack) { + @Nullable ReadableMap selectedVideoTrack) { String typeString = null; Dynamic value = null; if (selectedVideoTrack != null) { typeString = selectedVideoTrack.hasKey(PROP_SELECTED_VIDEO_TRACK_TYPE) - ? selectedVideoTrack.getString(PROP_SELECTED_VIDEO_TRACK_TYPE) : null; + ? selectedVideoTrack.getString(PROP_SELECTED_VIDEO_TRACK_TYPE) + : null; value = selectedVideoTrack.hasKey(PROP_SELECTED_VIDEO_TRACK_VALUE) - ? selectedVideoTrack.getDynamic(PROP_SELECTED_VIDEO_TRACK_VALUE) : null; + ? selectedVideoTrack.getDynamic(PROP_SELECTED_VIDEO_TRACK_VALUE) + : null; } videoView.setSelectedVideoTrack(typeString, value); } @ReactProp(name = PROP_SELECTED_AUDIO_TRACK) public void setSelectedAudioTrack(final ReactExoplayerView videoView, - @Nullable ReadableMap selectedAudioTrack) { + @Nullable ReadableMap selectedAudioTrack) { String typeString = null; Dynamic value = null; if (selectedAudioTrack != null) { typeString = selectedAudioTrack.hasKey(PROP_SELECTED_AUDIO_TRACK_TYPE) - ? selectedAudioTrack.getString(PROP_SELECTED_AUDIO_TRACK_TYPE) : null; + ? selectedAudioTrack.getString(PROP_SELECTED_AUDIO_TRACK_TYPE) + : null; value = selectedAudioTrack.hasKey(PROP_SELECTED_AUDIO_TRACK_VALUE) - ? selectedAudioTrack.getDynamic(PROP_SELECTED_AUDIO_TRACK_VALUE) : null; + ? selectedAudioTrack.getDynamic(PROP_SELECTED_AUDIO_TRACK_VALUE) + : null; } videoView.setSelectedAudioTrack(typeString, value); } @ReactProp(name = PROP_SELECTED_TEXT_TRACK) public void setSelectedTextTrack(final ReactExoplayerView videoView, - @Nullable ReadableMap selectedTextTrack) { + @Nullable ReadableMap selectedTextTrack) { String typeString = null; Dynamic value = null; if (selectedTextTrack != null) { typeString = selectedTextTrack.hasKey(PROP_SELECTED_TEXT_TRACK_TYPE) - ? selectedTextTrack.getString(PROP_SELECTED_TEXT_TRACK_TYPE) : null; + ? selectedTextTrack.getString(PROP_SELECTED_TEXT_TRACK_TYPE) + : null; value = selectedTextTrack.hasKey(PROP_SELECTED_TEXT_TRACK_VALUE) - ? selectedTextTrack.getDynamic(PROP_SELECTED_TEXT_TRACK_VALUE) : null; + ? selectedTextTrack.getDynamic(PROP_SELECTED_TEXT_TRACK_VALUE) + : null; } videoView.setSelectedTextTrack(typeString, value); } @ReactProp(name = PROP_TEXT_TRACKS) public void setPropTextTracks(final ReactExoplayerView videoView, - @Nullable ReadableArray textTracks) { + @Nullable ReadableArray textTracks) { videoView.setTextTracks(textTracks); } @@ -275,6 +279,11 @@ public void setMuted(final ReactExoplayerView videoView, final boolean muted) { videoView.setMutedModifier(muted); } + @ReactProp(name = PROP_AUDIO_OUTPUT) + public void setAudioOutput(final ReactExoplayerView videoView, final String audioOutput) { + videoView.setAudioOutput(ReactExoplayerView.AudioOutput.get(audioOutput)); + } + @ReactProp(name = PROP_VOLUME, defaultFloat = 1.0f) public void setVolume(final ReactExoplayerView videoView, final float volume) { videoView.setVolumeModifier(volume); @@ -387,20 +396,30 @@ public void setBufferConfig(final ReactExoplayerView videoView, @Nullable Readab if (bufferConfig != null) { minBufferMs = bufferConfig.hasKey(PROP_BUFFER_CONFIG_MIN_BUFFER_MS) - ? bufferConfig.getInt(PROP_BUFFER_CONFIG_MIN_BUFFER_MS) : minBufferMs; + ? bufferConfig.getInt(PROP_BUFFER_CONFIG_MIN_BUFFER_MS) + : minBufferMs; maxBufferMs = bufferConfig.hasKey(PROP_BUFFER_CONFIG_MAX_BUFFER_MS) - ? bufferConfig.getInt(PROP_BUFFER_CONFIG_MAX_BUFFER_MS) : maxBufferMs; + ? bufferConfig.getInt(PROP_BUFFER_CONFIG_MAX_BUFFER_MS) + : maxBufferMs; bufferForPlaybackMs = bufferConfig.hasKey(PROP_BUFFER_CONFIG_BUFFER_FOR_PLAYBACK_MS) - ? bufferConfig.getInt(PROP_BUFFER_CONFIG_BUFFER_FOR_PLAYBACK_MS) : bufferForPlaybackMs; - bufferForPlaybackAfterRebufferMs = bufferConfig.hasKey(PROP_BUFFER_CONFIG_BUFFER_FOR_PLAYBACK_AFTER_REBUFFER_MS) - ? bufferConfig.getInt(PROP_BUFFER_CONFIG_BUFFER_FOR_PLAYBACK_AFTER_REBUFFER_MS) : bufferForPlaybackAfterRebufferMs; + ? bufferConfig.getInt(PROP_BUFFER_CONFIG_BUFFER_FOR_PLAYBACK_MS) + : bufferForPlaybackMs; + bufferForPlaybackAfterRebufferMs = bufferConfig + .hasKey(PROP_BUFFER_CONFIG_BUFFER_FOR_PLAYBACK_AFTER_REBUFFER_MS) + ? bufferConfig.getInt(PROP_BUFFER_CONFIG_BUFFER_FOR_PLAYBACK_AFTER_REBUFFER_MS) + : bufferForPlaybackAfterRebufferMs; maxHeapAllocationPercent = bufferConfig.hasKey(PROP_BUFFER_CONFIG_MAX_HEAP_ALLOCATION_PERCENT) - ? bufferConfig.getDouble(PROP_BUFFER_CONFIG_MAX_HEAP_ALLOCATION_PERCENT) : maxHeapAllocationPercent; - minBackBufferMemoryReservePercent = bufferConfig.hasKey(PROP_BUFFER_CONFIG_MIN_BACK_BUFFER_MEMORY_RESERVE_PERCENT) - ? bufferConfig.getDouble(PROP_BUFFER_CONFIG_MIN_BACK_BUFFER_MEMORY_RESERVE_PERCENT) : minBackBufferMemoryReservePercent; + ? bufferConfig.getDouble(PROP_BUFFER_CONFIG_MAX_HEAP_ALLOCATION_PERCENT) + : maxHeapAllocationPercent; + minBackBufferMemoryReservePercent = bufferConfig + .hasKey(PROP_BUFFER_CONFIG_MIN_BACK_BUFFER_MEMORY_RESERVE_PERCENT) + ? bufferConfig.getDouble(PROP_BUFFER_CONFIG_MIN_BACK_BUFFER_MEMORY_RESERVE_PERCENT) + : minBackBufferMemoryReservePercent; minBufferMemoryReservePercent = bufferConfig.hasKey(PROP_BUFFER_CONFIG_MIN_BUFFER_MEMORY_RESERVE_PERCENT) - ? bufferConfig.getDouble(PROP_BUFFER_CONFIG_MIN_BUFFER_MEMORY_RESERVE_PERCENT) : minBufferMemoryReservePercent; - videoView.setBufferConfig(minBufferMs, maxBufferMs, bufferForPlaybackMs, bufferForPlaybackAfterRebufferMs, maxHeapAllocationPercent, minBackBufferMemoryReservePercent, minBufferMemoryReservePercent); + ? bufferConfig.getDouble(PROP_BUFFER_CONFIG_MIN_BUFFER_MEMORY_RESERVE_PERCENT) + : minBufferMemoryReservePercent; + videoView.setBufferConfig(minBufferMs, maxBufferMs, bufferForPlaybackMs, bufferForPlaybackAfterRebufferMs, + maxHeapAllocationPercent, minBackBufferMemoryReservePercent, minBufferMemoryReservePercent); } } @@ -426,7 +445,8 @@ private boolean startsWithValidScheme(String uriString) { * * @param readableMap The ReadableMap to be conveted. * @return A HashMap containing the data that was in the ReadableMap. - * @see 'Adapted from https://github.com/artemyarulin/react-native-eval/blob/master/android/src/main/java/com/evaluator/react/ConversionUtil.java' + * @see 'Adapted from + * https://github.com/artemyarulin/react-native-eval/blob/master/android/src/main/java/com/evaluator/react/ConversionUtil.java' */ public static Map toStringMap(@Nullable ReadableMap readableMap) { if (readableMap == null) diff --git a/ios/Video/Features/RCTPlayerOperations.swift b/ios/Video/Features/RCTPlayerOperations.swift index 8b76fafc5f..8c112155b7 100644 --- a/ios/Video/Features/RCTPlayerOperations.swift +++ b/ios/Video/Features/RCTPlayerOperations.swift @@ -197,7 +197,7 @@ enum RCTPlayerOperations { var options:AVAudioSession.CategoryOptions? = nil if (ignoreSilentSwitch == "ignore") { - category = AVAudioSession.Category.playback + category = AVAudioSession.Category.playAndRecord } else if (ignoreSilentSwitch == "obey") { category = AVAudioSession.Category.ambient } diff --git a/ios/Video/RCTVideo.swift b/ios/Video/RCTVideo.swift index 678623d424..8af0135369 100644 --- a/ios/Video/RCTVideo.swift +++ b/ios/Video/RCTVideo.swift @@ -35,6 +35,7 @@ class RCTVideo: UIView, RCTVideoPlayerViewControllerDelegate, RCTPlayerObserverH private var _controls:Bool = 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? @@ -515,6 +516,20 @@ class RCTVideo: UIView, RCTVideoPlayerViewControllerDelegate, RCTPlayerObserverH applyModifiers() } + @objc + func setAudioOutput(_ audioOutput:String) { + _audioOutput = audioOutput + do { + if audioOutput == "speaker" { + try AVAudioSession.sharedInstance().overrideOutputAudioPort(AVAudioSession.PortOverride.speaker) + } else if audioOutput == "earpiece" { + try AVAudioSession.sharedInstance().overrideOutputAudioPort(AVAudioSession.PortOverride.none) + } + } catch { + print("Error occurred: \(error.localizedDescription)") + } + } + @objc func setVolume(_ volume:Float) { _volume = volume diff --git a/ios/Video/RCTVideoManager.m b/ios/Video/RCTVideoManager.m index 2781656be7..8f577030f6 100644 --- a/ios/Video/RCTVideoManager.m +++ b/ios/Video/RCTVideoManager.m @@ -17,6 +17,7 @@ @interface RCT_EXTERN_MODULE(RCTVideoManager, RCTViewManager) RCT_EXPORT_VIEW_PROPERTY(paused, BOOL); RCT_EXPORT_VIEW_PROPERTY(muted, BOOL); RCT_EXPORT_VIEW_PROPERTY(controls, BOOL); +RCT_EXPORT_VIEW_PROPERTY(audioOutput, NSString); RCT_EXPORT_VIEW_PROPERTY(volume, float); RCT_EXPORT_VIEW_PROPERTY(playInBackground, BOOL); RCT_EXPORT_VIEW_PROPERTY(preventsDisplaySleepDuringVideoPlayback, BOOL); From 2228838075c8901acb53691fd1c7aeb23fec858e Mon Sep 17 00:00:00 2001 From: olivier Date: Wed, 5 Apr 2023 22:41:27 +0200 Subject: [PATCH 018/117] chore: fix changelog --- CHANGELOG.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ee5751619a..660d55731d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,10 +3,11 @@ ### Version 6.0.0-alpha.6 - iOS: remove undocumented `currentTime` property [#3064](https://github.com/react-native-video/react-native-video/pull/3064) +- iOS: make sure that the audio in ads is muted when the player is muted. [#3068](https://github.com/react-native-video/react-native-video/pull/3077) +- iOS: make IMA build optionnal ### Version 6.0.0-alpha.5 -- iOS: make sure that the audio in ads is muted when the player is muted. [#3068](https://github.com/react-native-video/react-native-video/pull/3077) - iOS: ensure controls are not displayed when disabled by user [#3017](https://github.com/react-native-video/react-native-video/pull/3017) - iOS: app crashes on call to presentFullScreenPlayer [#2808](https://github.com/react-native-video/react-native-video/pull/2971) - Android: Fix publicated progress handler causing duplicated progress event [#2972](https://github.com/react-native-video/react-native-video/pull/2972) From 87859bcc791d896826825e0bf1296bdd972e76ff Mon Sep 17 00:00:00 2001 From: Romick2005 <17779660+Romick2005@users.noreply.github.com> Date: Wed, 5 Apr 2023 23:50:22 +0300 Subject: [PATCH 019/117] Update ios/Video/Features/RCTPlayerOperations.swift Co-authored-by: Olivier Bouillet <62574056+freeboub@users.noreply.github.com> --- ios/Video/Features/RCTPlayerOperations.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ios/Video/Features/RCTPlayerOperations.swift b/ios/Video/Features/RCTPlayerOperations.swift index 453a0a0c05..a6156445f4 100644 --- a/ios/Video/Features/RCTPlayerOperations.swift +++ b/ios/Video/Features/RCTPlayerOperations.swift @@ -213,7 +213,7 @@ enum RCTPlayerOperations { try audioSession.setCategory(category, options: options) } catch { debugPrint("[RCTPlayerOperations] Problem setting up AVAudioSession category and options. Error: \(error).") - // Handle specific set category abd option combination error + // Handle specific set category and option combination error // setCategory:AVAudioSessionCategoryPlayback withOptions:mixWithOthers || duckOthers // Failed to set category, error: 'what' Error Domain=NSOSStatusErrorDomain // https://developer.apple.com/forums/thread/714598 From de8c5329163a33fbd7da45e80cdc6f3b7f6ab096 Mon Sep 17 00:00:00 2001 From: olivier Date: Wed, 5 Apr 2023 23:16:38 +0200 Subject: [PATCH 020/117] chore: add dummy test rule to ensure np works --- package.json | 1 + 1 file changed, 1 insertion(+) diff --git a/package.json b/package.json index aa671cd92f..9f25d438b2 100644 --- a/package.json +++ b/package.json @@ -6,6 +6,7 @@ "license": "MIT", "author": "Community Contributors", "homepage": "https://github.com/react-native-video/react-native-video#readme", + "test": "", "repository": { "type": "git", "url": "git@github.com:react-native-video/react-native-video.git" From d9404951eb0dfa6eb5fdc6866a68311ffe4d9979 Mon Sep 17 00:00:00 2001 From: olivier Date: Wed, 5 Apr 2023 23:21:11 +0200 Subject: [PATCH 021/117] chore: fix previous commit --- package.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index 9f25d438b2..4257f413d6 100644 --- a/package.json +++ b/package.json @@ -6,7 +6,6 @@ "license": "MIT", "author": "Community Contributors", "homepage": "https://github.com/react-native-video/react-native-video#readme", - "test": "", "repository": { "type": "git", "url": "git@github.com:react-native-video/react-native-video.git" @@ -25,7 +24,8 @@ }, "scripts": { "lint": "yarn eslint .", - "xbasic": "yarn --cwd examples/basic" + "xbasic": "yarn --cwd examples/basic", + "test": "echo no test available" }, "files": [ "android", From e08ef2d5abfc0eb597d42f9d375fa61fe2ac4020 Mon Sep 17 00:00:00 2001 From: olivier Date: Wed, 5 Apr 2023 23:23:39 +0200 Subject: [PATCH 022/117] 6.0.0-alpha.6 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 4257f413d6..4ffa860013 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "react-native-video", - "version": "6.0.0-alpha.5", + "version": "6.0.0-alpha.6", "description": "A