Merge remote-tracking branch 'upstream/master'
This commit is contained in:
commit
dc3e83a3d5
29
.github/ISSUE_TEMPLATE/bug_report.md
vendored
29
.github/ISSUE_TEMPLATE/bug_report.md
vendored
@ -10,37 +10,24 @@ assignees: ''
|
|||||||
# Bug
|
# Bug
|
||||||
|
|
||||||
<!--
|
<!--
|
||||||
Before opening a ticket
|
Very important, before opening a ticket:
|
||||||
* Ensure the issue has not been already reported
|
1) Ensure the issue has not been already reported
|
||||||
* Please test using the latest release of the library, as maybe said bug has been already fixed.
|
2) lease test using the latest release (including 6.0.0 preRelease) of the library, as maybe the bug has been already fixed.
|
||||||
* Provide a clear and concise description of what the bug is.
|
3) please don't use emulator to reproduce issues. if you have strange or sporadic error check if this is not devices specific (understand that this library is just a binding to player).
|
||||||
* If the library has multiple install methods, describe installation method (e.g., pod, not pod, with jetifier etc)
|
4) ensure you cannot solve the issue by yourself using following guide: https://github.com/react-native-video/react-native-video/blob/master/docs/DEBUGGING.md
|
||||||
* Include screenshots if needed.
|
|
||||||
-->
|
-->
|
||||||
|
|
||||||
## Platform
|
## Platform
|
||||||
<!--
|
|
||||||
Platform where your bug is happening.
|
|
||||||
-->
|
|
||||||
Which player are you experiencing the problem on:
|
Which player are you experiencing the problem on:
|
||||||
* iOS
|
* iOS
|
||||||
* Android
|
* Android
|
||||||
* Windows UWP
|
* Windows
|
||||||
* Windows WPF
|
|
||||||
|
|
||||||
## Environment info
|
## Environment info
|
||||||
|
|
||||||
<!--
|
<!-- This fields are mandatory -->
|
||||||
Run `react-native info` in your terminal and copy the results here. Also, include the *precise* version number of this library that you are using in the project
|
|
||||||
-->
|
|
||||||
|
|
||||||
React native info output:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
// paste it here
|
|
||||||
```
|
|
||||||
|
|
||||||
Library version: x.x.x
|
Library version: x.x.x
|
||||||
|
Device:
|
||||||
|
|
||||||
## Steps To Reproduce
|
## Steps To Reproduce
|
||||||
|
|
||||||
|
15
API.md
15
API.md
@ -935,6 +935,21 @@ The following other types are supported on some platforms, but aren't fully docu
|
|||||||
`content://, ms-appx://, ms-appdata://, assets-library://`
|
`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={{ 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
|
||||||
|
|
||||||
#### subtitleStyle
|
#### subtitleStyle
|
||||||
|
|
||||||
Property | Description | Platforms
|
Property | Description | Platforms
|
||||||
|
@ -2,6 +2,12 @@
|
|||||||
|
|
||||||
- Feature: playing audio over earpiece [#2887](https://github.com/react-native-video/react-native-video/issues/2887)
|
- Feature: playing audio over earpiece [#2887](https://github.com/react-native-video/react-native-video/issues/2887)
|
||||||
|
|
||||||
|
### Version 6.0.0-alpha.6
|
||||||
|
- Feature: Video range support [#3030](https://github.com/react-native-video/react-native-video/pull/3030)
|
||||||
|
- 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
|
### 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)
|
- iOS: ensure controls are not displayed when disabled by user [#3017](https://github.com/react-native-video/react-native-video/pull/3017)
|
||||||
|
23
Video.js
23
Video.js
@ -342,6 +342,8 @@ export default class Video extends Component {
|
|||||||
mainVer: source.mainVer || 0,
|
mainVer: source.mainVer || 0,
|
||||||
patchVer: source.patchVer || 0,
|
patchVer: source.patchVer || 0,
|
||||||
requestHeaders: source.headers ? this.stringsOnlyObject(source.headers) : {},
|
requestHeaders: source.headers ? this.stringsOnlyObject(source.headers) : {},
|
||||||
|
startTime: source.startTime || 0,
|
||||||
|
endTime: source.endTime
|
||||||
},
|
},
|
||||||
onVideoLoadStart: this._onLoadStart,
|
onVideoLoadStart: this._onLoadStart,
|
||||||
onVideoPlaybackStateChanged: this._onPlaybackStateChanged,
|
onVideoPlaybackStateChanged: this._onPlaybackStateChanged,
|
||||||
@ -414,13 +416,6 @@ Video.propTypes = {
|
|||||||
FilterType.SEPIA,
|
FilterType.SEPIA,
|
||||||
]),
|
]),
|
||||||
filterEnabled: PropTypes.bool,
|
filterEnabled: PropTypes.bool,
|
||||||
/* Native only */
|
|
||||||
src: PropTypes.object,
|
|
||||||
seek: PropTypes.oneOfType([
|
|
||||||
PropTypes.number,
|
|
||||||
PropTypes.object,
|
|
||||||
]),
|
|
||||||
fullscreen: PropTypes.bool,
|
|
||||||
onVideoLoadStart: PropTypes.func,
|
onVideoLoadStart: PropTypes.func,
|
||||||
onVideoLoad: PropTypes.func,
|
onVideoLoad: PropTypes.func,
|
||||||
onVideoBuffer: PropTypes.func,
|
onVideoBuffer: PropTypes.func,
|
||||||
@ -558,24 +553,12 @@ Video.propTypes = {
|
|||||||
onAudioFocusChanged: PropTypes.func,
|
onAudioFocusChanged: PropTypes.func,
|
||||||
onAudioBecomingNoisy: PropTypes.func,
|
onAudioBecomingNoisy: PropTypes.func,
|
||||||
onPictureInPictureStatusChanged: PropTypes.func,
|
onPictureInPictureStatusChanged: PropTypes.func,
|
||||||
needsToRestoreUserInterfaceForPictureInPictureStop: PropTypes.func,
|
|
||||||
onExternalPlaybackChange: PropTypes.func,
|
onExternalPlaybackChange: PropTypes.func,
|
||||||
adTagUrl: PropTypes.string,
|
adTagUrl: PropTypes.string,
|
||||||
onReceiveAdEvent: PropTypes.func,
|
onReceiveAdEvent: PropTypes.func,
|
||||||
|
|
||||||
/* Required by react-native */
|
/* Required by react-native */
|
||||||
scaleX: PropTypes.number,
|
|
||||||
scaleY: PropTypes.number,
|
|
||||||
translateX: PropTypes.number,
|
|
||||||
translateY: PropTypes.number,
|
|
||||||
rotation: PropTypes.number,
|
|
||||||
...ViewPropTypes,
|
...ViewPropTypes,
|
||||||
};
|
};
|
||||||
|
|
||||||
const RCTVideo = requireNativeComponent('RCTVideo', Video, {
|
const RCTVideo = requireNativeComponent('RCTVideo');
|
||||||
nativeOnly: {
|
|
||||||
src: true,
|
|
||||||
seek: true,
|
|
||||||
fullscreen: true,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
@ -17,6 +17,7 @@ def configStringPath = (
|
|||||||
).md5()
|
).md5()
|
||||||
|
|
||||||
android {
|
android {
|
||||||
|
namespace 'com.brentvatne.react'
|
||||||
compileSdkVersion safeExtGet('compileSdkVersion', 31)
|
compileSdkVersion safeExtGet('compileSdkVersion', 31)
|
||||||
buildToolsVersion safeExtGet('buildToolsVersion', '30.0.2')
|
buildToolsVersion safeExtGet('buildToolsVersion', '30.0.2')
|
||||||
|
|
||||||
|
@ -4,6 +4,7 @@ import static com.google.android.exoplayer2.C.CONTENT_TYPE_DASH;
|
|||||||
import static com.google.android.exoplayer2.C.CONTENT_TYPE_HLS;
|
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_OTHER;
|
||||||
import static com.google.android.exoplayer2.C.CONTENT_TYPE_SS;
|
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.annotation.SuppressLint;
|
||||||
import android.app.Activity;
|
import android.app.Activity;
|
||||||
@ -94,6 +95,7 @@ import com.google.android.exoplayer2.source.dash.manifest.Representation;
|
|||||||
import com.google.android.exoplayer2.ext.ima.ImaAdsLoader;
|
import com.google.android.exoplayer2.ext.ima.ImaAdsLoader;
|
||||||
import com.google.android.exoplayer2.source.ads.AdsMediaSource;
|
import com.google.android.exoplayer2.source.ads.AdsMediaSource;
|
||||||
import com.google.android.exoplayer2.source.DefaultMediaSourceFactory;
|
import com.google.android.exoplayer2.source.DefaultMediaSourceFactory;
|
||||||
|
import com.google.android.exoplayer2.source.ClippingMediaSource;
|
||||||
|
|
||||||
import com.google.common.collect.ImmutableList;
|
import com.google.common.collect.ImmutableList;
|
||||||
import java.net.CookieHandler;
|
import java.net.CookieHandler;
|
||||||
@ -210,6 +212,8 @@ class ReactExoplayerView extends FrameLayout implements
|
|||||||
// Props from React
|
// Props from React
|
||||||
private int backBufferDurationMs = DefaultLoadControl.DEFAULT_BACK_BUFFER_DURATION_MS;
|
private int backBufferDurationMs = DefaultLoadControl.DEFAULT_BACK_BUFFER_DURATION_MS;
|
||||||
private Uri srcUri;
|
private Uri srcUri;
|
||||||
|
private long startTimeMs = -1;
|
||||||
|
private long endTimeMs = -1;
|
||||||
private String extension;
|
private String extension;
|
||||||
private boolean repeat;
|
private boolean repeat;
|
||||||
private String audioTrackType;
|
private String audioTrackType;
|
||||||
@ -717,7 +721,8 @@ class ReactExoplayerView extends FrameLayout implements
|
|||||||
|
|
||||||
private void initializePlayerSource(ReactExoplayerView self, DrmSessionManager drmSessionManager) {
|
private void initializePlayerSource(ReactExoplayerView self, DrmSessionManager drmSessionManager) {
|
||||||
ArrayList<MediaSource> mediaSourceList = buildTextSources();
|
ArrayList<MediaSource> mediaSourceList = buildTextSources();
|
||||||
MediaSource videoSource = buildMediaSource(self.srcUri, self.extension, drmSessionManager);
|
MediaSource videoSource = buildMediaSource(self.srcUri, self.extension, drmSessionManager, startTimeMs,
|
||||||
|
endTimeMs);
|
||||||
MediaSource mediaSourceWithAds = null;
|
MediaSource mediaSourceWithAds = null;
|
||||||
if (adTagUrl != null) {
|
if (adTagUrl != null) {
|
||||||
MediaSource.Factory mediaSourceFactory = new DefaultMediaSourceFactory(mediaDataSourceFactory)
|
MediaSource.Factory mediaSourceFactory = new DefaultMediaSourceFactory(mediaDataSourceFactory)
|
||||||
@ -815,7 +820,8 @@ class ReactExoplayerView extends FrameLayout implements
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
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) {
|
if (uri == null) {
|
||||||
throw new IllegalStateException("Invalid video uri");
|
throw new IllegalStateException("Invalid video uri");
|
||||||
}
|
}
|
||||||
@ -831,7 +837,7 @@ class ReactExoplayerView extends FrameLayout implements
|
|||||||
}
|
}
|
||||||
|
|
||||||
MediaItem mediaItem = mediaItemBuilder.build();
|
MediaItem mediaItem = mediaItemBuilder.build();
|
||||||
|
MediaSource mediaSource = null;
|
||||||
DrmSessionManagerProvider drmProvider = null;
|
DrmSessionManagerProvider drmProvider = null;
|
||||||
if (drmSessionManager != null) {
|
if (drmSessionManager != null) {
|
||||||
drmProvider = new DrmSessionManagerProvider() {
|
drmProvider = new DrmSessionManagerProvider() {
|
||||||
@ -845,35 +851,49 @@ class ReactExoplayerView extends FrameLayout implements
|
|||||||
}
|
}
|
||||||
switch (type) {
|
switch (type) {
|
||||||
case CONTENT_TYPE_SS:
|
case CONTENT_TYPE_SS:
|
||||||
return new SsMediaSource.Factory(
|
mediaSource = new SsMediaSource.Factory(
|
||||||
new DefaultSsChunkSource.Factory(mediaDataSourceFactory),
|
new DefaultSsChunkSource.Factory(mediaDataSourceFactory),
|
||||||
buildDataSourceFactory(false)).setDrmSessionManagerProvider(drmProvider)
|
buildDataSourceFactory(false)).setDrmSessionManagerProvider(drmProvider)
|
||||||
.setLoadErrorHandlingPolicy(
|
.setLoadErrorHandlingPolicy(
|
||||||
config.buildLoadErrorHandlingPolicy(minLoadRetryCount))
|
config.buildLoadErrorHandlingPolicy(minLoadRetryCount))
|
||||||
.createMediaSource(mediaItem);
|
.createMediaSource(mediaItem);
|
||||||
|
break;
|
||||||
case CONTENT_TYPE_DASH:
|
case CONTENT_TYPE_DASH:
|
||||||
return new DashMediaSource.Factory(
|
mediaSource = new DashMediaSource.Factory(
|
||||||
new DefaultDashChunkSource.Factory(mediaDataSourceFactory),
|
new DefaultDashChunkSource.Factory(mediaDataSourceFactory),
|
||||||
buildDataSourceFactory(false)).setDrmSessionManagerProvider(drmProvider)
|
buildDataSourceFactory(false)).setDrmSessionManagerProvider(drmProvider)
|
||||||
.setLoadErrorHandlingPolicy(
|
.setLoadErrorHandlingPolicy(
|
||||||
config.buildLoadErrorHandlingPolicy(minLoadRetryCount))
|
config.buildLoadErrorHandlingPolicy(minLoadRetryCount))
|
||||||
.createMediaSource(mediaItem);
|
.createMediaSource(mediaItem);
|
||||||
|
break;
|
||||||
case CONTENT_TYPE_HLS:
|
case CONTENT_TYPE_HLS:
|
||||||
return new HlsMediaSource.Factory(
|
mediaSource = new HlsMediaSource.Factory(
|
||||||
mediaDataSourceFactory).setDrmSessionManagerProvider(drmProvider)
|
mediaDataSourceFactory).setDrmSessionManagerProvider(drmProvider)
|
||||||
.setLoadErrorHandlingPolicy(
|
.setLoadErrorHandlingPolicy(
|
||||||
config.buildLoadErrorHandlingPolicy(minLoadRetryCount))
|
config.buildLoadErrorHandlingPolicy(minLoadRetryCount))
|
||||||
.createMediaSource(mediaItem);
|
.createMediaSource(mediaItem);
|
||||||
|
break;
|
||||||
case CONTENT_TYPE_OTHER:
|
case CONTENT_TYPE_OTHER:
|
||||||
return new ProgressiveMediaSource.Factory(
|
mediaSource = new ProgressiveMediaSource.Factory(
|
||||||
mediaDataSourceFactory).setDrmSessionManagerProvider(drmProvider)
|
mediaDataSourceFactory).setDrmSessionManagerProvider(drmProvider)
|
||||||
.setLoadErrorHandlingPolicy(
|
.setLoadErrorHandlingPolicy(
|
||||||
config.buildLoadErrorHandlingPolicy(minLoadRetryCount))
|
config.buildLoadErrorHandlingPolicy(minLoadRetryCount))
|
||||||
.createMediaSource(mediaItem);
|
.createMediaSource(mediaItem);
|
||||||
|
break;
|
||||||
default: {
|
default: {
|
||||||
throw new IllegalStateException("Unsupported type: " + type);
|
throw new IllegalStateException("Unsupported type: " + type);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
}
|
}
|
||||||
|
|
||||||
private ArrayList<MediaSource> buildTextSources() {
|
private ArrayList<MediaSource> buildTextSources() {
|
||||||
@ -1530,11 +1550,15 @@ class ReactExoplayerView extends FrameLayout implements
|
|||||||
|
|
||||||
// ReactExoplayerViewManager public api
|
// ReactExoplayerViewManager public api
|
||||||
|
|
||||||
public void setSrc(final Uri uri, final String extension, Map<String, String> headers) {
|
public void setSrc(final Uri uri, final long startTimeMs, final long endTimeMs, final String extension,
|
||||||
|
Map<String, String> headers) {
|
||||||
if (uri != null) {
|
if (uri != null) {
|
||||||
boolean isSourceEqual = uri.equals(srcUri);
|
boolean isSourceEqual = uri.equals(srcUri) && startTimeMs == this.startTimeMs
|
||||||
|
&& endTimeMs == this.endTimeMs;
|
||||||
hasDrmFailed = false;
|
hasDrmFailed = false;
|
||||||
this.srcUri = uri;
|
this.srcUri = uri;
|
||||||
|
this.startTimeMs = startTimeMs;
|
||||||
|
this.endTimeMs = endTimeMs;
|
||||||
this.extension = extension;
|
this.extension = extension;
|
||||||
this.requestHeaders = headers;
|
this.requestHeaders = headers;
|
||||||
this.mediaDataSourceFactory = DataSourceUtil.getDefaultDataSourceFactory(this.themedReactContext,
|
this.mediaDataSourceFactory = DataSourceUtil.getDefaultDataSourceFactory(this.themedReactContext,
|
||||||
@ -1552,6 +1576,8 @@ class ReactExoplayerView extends FrameLayout implements
|
|||||||
player.stop();
|
player.stop();
|
||||||
player.clearMediaItems();
|
player.clearMediaItems();
|
||||||
this.srcUri = null;
|
this.srcUri = null;
|
||||||
|
this.startTimeMs = -1;
|
||||||
|
this.endTimeMs = -1;
|
||||||
this.extension = null;
|
this.extension = null;
|
||||||
this.requestHeaders = null;
|
this.requestHeaders = null;
|
||||||
this.mediaDataSourceFactory = null;
|
this.mediaDataSourceFactory = null;
|
||||||
|
@ -28,9 +28,10 @@ import javax.annotation.Nullable;
|
|||||||
public class ReactExoplayerViewManager extends ViewGroupManager<ReactExoplayerView> {
|
public class ReactExoplayerViewManager extends ViewGroupManager<ReactExoplayerView> {
|
||||||
|
|
||||||
private static final String REACT_CLASS = "RCTVideo";
|
private static final String REACT_CLASS = "RCTVideo";
|
||||||
|
|
||||||
private static final String PROP_SRC = "src";
|
private static final String PROP_SRC = "src";
|
||||||
private static final String PROP_SRC_URI = "uri";
|
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_AD_TAG_URL = "adTagUrl";
|
||||||
private static final String PROP_SRC_TYPE = "type";
|
private static final String PROP_SRC_TYPE = "type";
|
||||||
private static final String PROP_DRM = "drm";
|
private static final String PROP_DRM = "drm";
|
||||||
@ -152,6 +153,8 @@ public class ReactExoplayerViewManager extends ViewGroupManager<ReactExoplayerVi
|
|||||||
public void setSrc(final ReactExoplayerView videoView, @Nullable ReadableMap src) {
|
public void setSrc(final ReactExoplayerView videoView, @Nullable ReadableMap src) {
|
||||||
Context context = videoView.getContext().getApplicationContext();
|
Context context = videoView.getContext().getApplicationContext();
|
||||||
String uriString = src.hasKey(PROP_SRC_URI) ? src.getString(PROP_SRC_URI) : null;
|
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;
|
String extension = src.hasKey(PROP_SRC_TYPE) ? src.getString(PROP_SRC_TYPE) : null;
|
||||||
Map<String, String> headers = src.hasKey(PROP_SRC_HEADERS) ? toStringMap(src.getMap(PROP_SRC_HEADERS)) : null;
|
Map<String, String> headers = src.hasKey(PROP_SRC_HEADERS) ? toStringMap(src.getMap(PROP_SRC_HEADERS)) : null;
|
||||||
|
|
||||||
@ -164,7 +167,7 @@ public class ReactExoplayerViewManager extends ViewGroupManager<ReactExoplayerVi
|
|||||||
Uri srcUri = Uri.parse(uriString);
|
Uri srcUri = Uri.parse(uriString);
|
||||||
|
|
||||||
if (srcUri != null) {
|
if (srcUri != null) {
|
||||||
videoView.setSrc(srcUri, extension, headers);
|
videoView.setSrc(srcUri, startTimeMs, endTimeMs, extension, headers);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
int identifier = context.getResources().getIdentifier(
|
int identifier = context.getResources().getIdentifier(
|
||||||
|
@ -14,27 +14,27 @@
|
|||||||
android:paddingTop="4dp"
|
android:paddingTop="4dp"
|
||||||
android:orientation="horizontal">
|
android:orientation="horizontal">
|
||||||
|
|
||||||
<ImageButton android:id="@id/exo_prev"
|
<ImageButton android:id="@+id/exo_prev"
|
||||||
style="@style/ExoMediaButton.Previous"/>
|
style="@style/ExoMediaButton.Previous"/>
|
||||||
|
|
||||||
<ImageButton android:id="@id/exo_rew"
|
<ImageButton android:id="@+id/exo_rew"
|
||||||
style="@style/ExoMediaButton.Rewind"/>
|
style="@style/ExoMediaButton.Rewind"/>
|
||||||
<FrameLayout
|
<FrameLayout
|
||||||
android:id="@+id/exo_play_pause_container"
|
android:id="@+id/exo_play_pause_container"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_gravity="center">
|
android:layout_gravity="center">
|
||||||
<ImageButton android:id="@id/exo_play"
|
<ImageButton android:id="@+id/exo_play"
|
||||||
style="@style/ExoMediaButton.Play"/>
|
style="@style/ExoMediaButton.Play"/>
|
||||||
|
|
||||||
<ImageButton android:id="@id/exo_pause"
|
<ImageButton android:id="@+id/exo_pause"
|
||||||
style="@style/ExoMediaButton.Pause"/>
|
style="@style/ExoMediaButton.Pause"/>
|
||||||
</FrameLayout>
|
</FrameLayout>
|
||||||
|
|
||||||
<ImageButton android:id="@id/exo_ffwd"
|
<ImageButton android:id="@+id/exo_ffwd"
|
||||||
style="@style/ExoMediaButton.FastForward"/>
|
style="@style/ExoMediaButton.FastForward"/>
|
||||||
|
|
||||||
<ImageButton android:id="@id/exo_next"
|
<ImageButton android:id="@+id/exo_next"
|
||||||
style="@style/ExoMediaButton.Next"/>
|
style="@style/ExoMediaButton.Next"/>
|
||||||
|
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
@ -46,7 +46,7 @@
|
|||||||
android:gravity="center_vertical"
|
android:gravity="center_vertical"
|
||||||
android:orientation="horizontal">
|
android:orientation="horizontal">
|
||||||
|
|
||||||
<TextView android:id="@id/exo_position"
|
<TextView android:id="@+id/exo_position"
|
||||||
android:layout_width="50dp"
|
android:layout_width="50dp"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:textSize="14sp"
|
android:textSize="14sp"
|
||||||
@ -57,12 +57,12 @@
|
|||||||
android:textColor="#FFBEBEBE"/>
|
android:textColor="#FFBEBEBE"/>
|
||||||
|
|
||||||
<com.google.android.exoplayer2.ui.DefaultTimeBar
|
<com.google.android.exoplayer2.ui.DefaultTimeBar
|
||||||
android:id="@id/exo_progress"
|
android:id="@+id/exo_progress"
|
||||||
android:layout_width="0dp"
|
android:layout_width="0dp"
|
||||||
android:layout_weight="1"
|
android:layout_weight="1"
|
||||||
android:layout_height="26dp"/>
|
android:layout_height="26dp"/>
|
||||||
|
|
||||||
<TextView android:id="@id/exo_duration"
|
<TextView android:id="@+id/exo_duration"
|
||||||
android:layout_width="50dp"
|
android:layout_width="50dp"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:textSize="14sp"
|
android:textSize="14sp"
|
||||||
@ -73,7 +73,7 @@
|
|||||||
android:textColor="#FFBEBEBE"/>
|
android:textColor="#FFBEBEBE"/>
|
||||||
|
|
||||||
<ImageButton
|
<ImageButton
|
||||||
android:id="@id/exo_fullscreen"
|
android:id="@+id/exo_fullscreen"
|
||||||
style="@style/ExoMediaButton.FullScreen"
|
style="@style/ExoMediaButton.FullScreen"
|
||||||
android:layout_width="30dp"
|
android:layout_width="30dp"
|
||||||
android:layout_height="30dp"
|
android:layout_height="30dp"
|
||||||
|
@ -6,6 +6,8 @@ struct VideoSource {
|
|||||||
let isAsset: Bool
|
let isAsset: Bool
|
||||||
let shouldCache: Bool
|
let shouldCache: Bool
|
||||||
let requestHeaders: Dictionary<String,Any>?
|
let requestHeaders: Dictionary<String,Any>?
|
||||||
|
let startTime: Int64?
|
||||||
|
let endTime: Int64?
|
||||||
|
|
||||||
let json: NSDictionary?
|
let json: NSDictionary?
|
||||||
|
|
||||||
@ -18,6 +20,8 @@ struct VideoSource {
|
|||||||
self.isAsset = false
|
self.isAsset = false
|
||||||
self.shouldCache = false
|
self.shouldCache = false
|
||||||
self.requestHeaders = nil
|
self.requestHeaders = nil
|
||||||
|
self.startTime = nil
|
||||||
|
self.endTime = nil
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
self.json = json
|
self.json = json
|
||||||
@ -27,5 +31,7 @@ struct VideoSource {
|
|||||||
self.isAsset = json["isAsset"] as? Bool ?? false
|
self.isAsset = json["isAsset"] as? Bool ?? false
|
||||||
self.shouldCache = json["shouldCache"] as? Bool ?? false
|
self.shouldCache = json["shouldCache"] as? Bool ?? false
|
||||||
self.requestHeaders = json["requestHeaders"] as? Dictionary<String,Any>
|
self.requestHeaders = json["requestHeaders"] as? Dictionary<String,Any>
|
||||||
|
self.startTime = json["startTime"] as? Int64
|
||||||
|
self.endTime = json["endTime"] as? Int64
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -4,7 +4,7 @@ import GoogleInteractiveMediaAds
|
|||||||
|
|
||||||
class RCTIMAAdsManager: NSObject, IMAAdsLoaderDelegate, IMAAdsManagerDelegate {
|
class RCTIMAAdsManager: NSObject, IMAAdsLoaderDelegate, IMAAdsManagerDelegate {
|
||||||
|
|
||||||
private var _video:RCTVideo
|
private weak var _video: RCTVideo?
|
||||||
|
|
||||||
/* Entry point for the SDK. Used to make ad requests. */
|
/* Entry point for the SDK. Used to make ad requests. */
|
||||||
private var adsLoader: IMAAdsLoader!
|
private var adsLoader: IMAAdsLoader!
|
||||||
@ -23,6 +23,7 @@ class RCTIMAAdsManager: NSObject, IMAAdsLoaderDelegate, IMAAdsManagerDelegate {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func requestAds() {
|
func requestAds() {
|
||||||
|
guard let _video = _video else {return}
|
||||||
// Create ad display container for ad rendering.
|
// Create ad display container for ad rendering.
|
||||||
let adDisplayContainer = IMAAdDisplayContainer(adContainer: _video, viewController: _video.reactViewController())
|
let adDisplayContainer = IMAAdDisplayContainer(adContainer: _video, viewController: _video.reactViewController())
|
||||||
|
|
||||||
@ -54,6 +55,7 @@ class RCTIMAAdsManager: NSObject, IMAAdsLoaderDelegate, IMAAdsManagerDelegate {
|
|||||||
// MARK: - IMAAdsLoaderDelegate
|
// MARK: - IMAAdsLoaderDelegate
|
||||||
|
|
||||||
func adsLoader(_ loader: IMAAdsLoader, adsLoadedWith adsLoadedData: IMAAdsLoadedData) {
|
func adsLoader(_ loader: IMAAdsLoader, adsLoadedWith adsLoadedData: IMAAdsLoadedData) {
|
||||||
|
guard let _video = _video else {return}
|
||||||
// Grab the instance of the IMAAdsManager and set yourself as the delegate.
|
// Grab the instance of the IMAAdsManager and set yourself as the delegate.
|
||||||
adsManager = adsLoadedData.adsManager
|
adsManager = adsLoadedData.adsManager
|
||||||
adsManager?.delegate = self
|
adsManager?.delegate = self
|
||||||
@ -71,12 +73,17 @@ class RCTIMAAdsManager: NSObject, IMAAdsLoaderDelegate, IMAAdsManagerDelegate {
|
|||||||
print("Error loading ads: " + adErrorData.adError.message!)
|
print("Error loading ads: " + adErrorData.adError.message!)
|
||||||
}
|
}
|
||||||
|
|
||||||
_video.setPaused(false)
|
_video?.setPaused(false)
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: - IMAAdsManagerDelegate
|
// MARK: - IMAAdsManagerDelegate
|
||||||
|
|
||||||
func adsManager(_ adsManager: IMAAdsManager, didReceive event: IMAAdEvent) {
|
func adsManager(_ adsManager: IMAAdsManager, didReceive event: IMAAdEvent) {
|
||||||
|
guard let _video = _video else {return}
|
||||||
|
// Mute ad if the main player is muted
|
||||||
|
if (_video.isMuted()) {
|
||||||
|
adsManager.volume = 0;
|
||||||
|
}
|
||||||
// Play each ad once it has been loaded
|
// Play each ad once it has been loaded
|
||||||
if event.type == IMAAdEventType.LOADED {
|
if event.type == IMAAdEventType.LOADED {
|
||||||
adsManager.start()
|
adsManager.start()
|
||||||
@ -98,19 +105,19 @@ class RCTIMAAdsManager: NSObject, IMAAdsLoaderDelegate, IMAAdsManagerDelegate {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Fall back to playing content
|
// Fall back to playing content
|
||||||
_video.setPaused(false)
|
_video?.setPaused(false)
|
||||||
}
|
}
|
||||||
|
|
||||||
func adsManagerDidRequestContentPause(_ adsManager: IMAAdsManager) {
|
func adsManagerDidRequestContentPause(_ adsManager: IMAAdsManager) {
|
||||||
// Pause the content for the SDK to play ads.
|
// Pause the content for the SDK to play ads.
|
||||||
_video.setPaused(true)
|
_video?.setPaused(true)
|
||||||
_video.setAdPlaying(true)
|
_video?.setAdPlaying(true)
|
||||||
}
|
}
|
||||||
|
|
||||||
func adsManagerDidRequestContentResume(_ adsManager: IMAAdsManager) {
|
func adsManagerDidRequestContentResume(_ adsManager: IMAAdsManager) {
|
||||||
// Resume the content since the SDK is done playing ads (at least for now).
|
// Resume the content since the SDK is done playing ads (at least for now).
|
||||||
_video.setAdPlaying(false)
|
_video?.setAdPlaying(false)
|
||||||
_video.setPaused(false)
|
_video?.setPaused(false)
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: - Helpers
|
// MARK: - Helpers
|
||||||
|
@ -25,7 +25,7 @@ protocol RCTPlayerObserverHandler: RCTPlayerObserverHandlerObjc {
|
|||||||
|
|
||||||
class RCTPlayerObserver: NSObject {
|
class RCTPlayerObserver: NSObject {
|
||||||
|
|
||||||
var _handlers: RCTPlayerObserverHandler!
|
weak var _handlers: RCTPlayerObserverHandler?
|
||||||
|
|
||||||
var player:AVPlayer? {
|
var player:AVPlayer? {
|
||||||
willSet {
|
willSet {
|
||||||
@ -84,11 +84,13 @@ class RCTPlayerObserver: NSObject {
|
|||||||
private var _playerViewControllerOverlayFrameObserver:NSKeyValueObservation?
|
private var _playerViewControllerOverlayFrameObserver:NSKeyValueObservation?
|
||||||
|
|
||||||
deinit {
|
deinit {
|
||||||
NotificationCenter.default.removeObserver(_handlers)
|
if let _handlers = _handlers {
|
||||||
|
NotificationCenter.default.removeObserver(_handlers)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func addPlayerObservers() {
|
func addPlayerObservers() {
|
||||||
guard let player = player else {
|
guard let player = player, let _handlers = _handlers else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -102,7 +104,7 @@ class RCTPlayerObserver: NSObject {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func addPlayerItemObservers() {
|
func addPlayerItemObservers() {
|
||||||
guard let playerItem = playerItem else { return }
|
guard let playerItem = playerItem, let _handlers = _handlers else { return }
|
||||||
|
|
||||||
_playerItemStatusObserver = playerItem.observe(\.status, options: [.new, .old], changeHandler: _handlers.handlePlayerItemStatusChange)
|
_playerItemStatusObserver = playerItem.observe(\.status, options: [.new, .old], changeHandler: _handlers.handlePlayerItemStatusChange)
|
||||||
_playerPlaybackBufferEmptyObserver = playerItem.observe(\.isPlaybackBufferEmpty, options: [.new, .old], changeHandler: _handlers.handlePlaybackBufferKeyEmpty)
|
_playerPlaybackBufferEmptyObserver = playerItem.observe(\.isPlaybackBufferEmpty, options: [.new, .old], changeHandler: _handlers.handlePlaybackBufferKeyEmpty)
|
||||||
@ -118,7 +120,7 @@ class RCTPlayerObserver: NSObject {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func addPlayerViewControllerObservers() {
|
func addPlayerViewControllerObservers() {
|
||||||
guard let playerViewController = playerViewController else { return }
|
guard let playerViewController = playerViewController, let _handlers = _handlers else { return }
|
||||||
|
|
||||||
_playerViewControllerReadyForDisplayObserver = playerViewController.observe(\.isReadyForDisplay, options: [.new], changeHandler: _handlers.handleReadyForDisplay)
|
_playerViewControllerReadyForDisplayObserver = playerViewController.observe(\.isReadyForDisplay, options: [.new], changeHandler: _handlers.handleReadyForDisplay)
|
||||||
|
|
||||||
@ -131,6 +133,7 @@ class RCTPlayerObserver: NSObject {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func addPlayerLayerObserver() {
|
func addPlayerLayerObserver() {
|
||||||
|
guard let _handlers = _handlers else {return}
|
||||||
_playerLayerReadyForDisplayObserver = playerLayer?.observe(\.isReadyForDisplay, options: [.new], changeHandler: _handlers.handleReadyForDisplay)
|
_playerLayerReadyForDisplayObserver = playerLayer?.observe(\.isReadyForDisplay, options: [.new], changeHandler: _handlers.handleReadyForDisplay)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -139,6 +142,7 @@ class RCTPlayerObserver: NSObject {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func addPlayerTimeObserver() {
|
func addPlayerTimeObserver() {
|
||||||
|
guard let _handlers = _handlers else {return}
|
||||||
removePlayerTimeObserver()
|
removePlayerTimeObserver()
|
||||||
let progressUpdateIntervalMS:Float64 = _progressUpdateInterval / 1000
|
let progressUpdateIntervalMS:Float64 = _progressUpdateInterval / 1000
|
||||||
// @see endScrubbing in AVPlayerDemoPlaybackViewController.m
|
// @see endScrubbing in AVPlayerDemoPlaybackViewController.m
|
||||||
@ -174,6 +178,7 @@ class RCTPlayerObserver: NSObject {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func attachPlayerEventListeners() {
|
func attachPlayerEventListeners() {
|
||||||
|
guard let _handlers = _handlers else {return}
|
||||||
|
|
||||||
NotificationCenter.default.removeObserver(_handlers,
|
NotificationCenter.default.removeObserver(_handlers,
|
||||||
name:NSNotification.Name.AVPlayerItemDidPlayToEndTime,
|
name:NSNotification.Name.AVPlayerItemDidPlayToEndTime,
|
||||||
@ -202,6 +207,8 @@ class RCTPlayerObserver: NSObject {
|
|||||||
func clearPlayer() {
|
func clearPlayer() {
|
||||||
player = nil
|
player = nil
|
||||||
playerItem = nil
|
playerItem = nil
|
||||||
NotificationCenter.default.removeObserver(_handlers)
|
if let _handlers = _handlers {
|
||||||
|
NotificationCenter.default.removeObserver(_handlers)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -192,36 +192,51 @@ enum RCTPlayerOperations {
|
|||||||
}
|
}
|
||||||
|
|
||||||
static func configureAudio(ignoreSilentSwitch:String, mixWithOthers:String) {
|
static func configureAudio(ignoreSilentSwitch:String, mixWithOthers:String) {
|
||||||
let session:AVAudioSession! = AVAudioSession.sharedInstance()
|
let audioSession:AVAudioSession! = AVAudioSession.sharedInstance()
|
||||||
var category:AVAudioSession.Category? = nil
|
var category:AVAudioSession.Category? = nil
|
||||||
var options:AVAudioSession.CategoryOptions? = nil
|
var options:AVAudioSession.CategoryOptions? = nil
|
||||||
|
|
||||||
if (ignoreSilentSwitch == "ignore") {
|
if (ignoreSilentSwitch == "ignore") {
|
||||||
category = AVAudioSession.Category.playAndRecord
|
category = AVAudioSession.Category.playAndRecord
|
||||||
} else if (ignoreSilentSwitch == "obey") {
|
} else if (ignoreSilentSwitch == "obey") {
|
||||||
category = AVAudioSession.Category.ambient
|
category = AVAudioSession.Category.ambient
|
||||||
}
|
}
|
||||||
|
|
||||||
if (mixWithOthers == "mix") {
|
if (mixWithOthers == "mix") {
|
||||||
options = .mixWithOthers
|
options = .mixWithOthers
|
||||||
} else if (mixWithOthers == "duck") {
|
} else if (mixWithOthers == "duck") {
|
||||||
options = .duckOthers
|
options = .duckOthers
|
||||||
}
|
}
|
||||||
|
|
||||||
if let category = category, let options = options {
|
if let category = category, let options = options {
|
||||||
do {
|
do {
|
||||||
try session.setCategory(category, options: options)
|
try audioSession.setCategory(category, options: options)
|
||||||
} catch {
|
} catch {
|
||||||
|
debugPrint("[RCTPlayerOperations] Problem setting up AVAudioSession category and options. Error: \(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
|
||||||
|
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 {
|
} else if let category = category, options == nil {
|
||||||
do {
|
do {
|
||||||
try session.setCategory(category)
|
try audioSession.setCategory(category)
|
||||||
} catch {
|
} catch {
|
||||||
|
debugPrint("[RCTPlayerOperations] Problem setting up AVAudioSession category. Error: \(error).")
|
||||||
}
|
}
|
||||||
} else if category == nil, let options = options {
|
} else if category == nil, let options = options {
|
||||||
do {
|
do {
|
||||||
try session.setCategory(session.category, options: options)
|
try audioSession.setCategory(audioSession.category, options: options)
|
||||||
} catch {
|
} catch {
|
||||||
|
debugPrint("[RCTPlayerOperations] Problem setting up AVAudioSession options. Error: \(error).")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -12,13 +12,17 @@ enum RCTVideoUtils {
|
|||||||
*
|
*
|
||||||
* \returns The playable duration of the current player item in seconds.
|
* \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,
|
guard let player = player,
|
||||||
let video:AVPlayerItem = player.currentItem,
|
let video:AVPlayerItem = player.currentItem,
|
||||||
video.status == AVPlayerItem.Status.readyToPlay else {
|
video.status == AVPlayerItem.Status.readyToPlay else {
|
||||||
return 0
|
return 0
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (source?.startTime != nil && source?.endTime != nil) {
|
||||||
|
return NSNumber(value: (Float64(source?.endTime ?? 0) - Float64(source?.startTime ?? 0)) / 1000)
|
||||||
|
}
|
||||||
|
|
||||||
var effectiveTimeRange:CMTimeRange?
|
var effectiveTimeRange:CMTimeRange?
|
||||||
for (_, value) in video.loadedTimeRanges.enumerated() {
|
for (_, value) in video.loadedTimeRanges.enumerated() {
|
||||||
let timeRange:CMTimeRange = value.timeRangeValue
|
let timeRange:CMTimeRange = value.timeRangeValue
|
||||||
@ -31,6 +35,10 @@ enum RCTVideoUtils {
|
|||||||
if let effectiveTimeRange = effectiveTimeRange {
|
if let effectiveTimeRange = effectiveTimeRange {
|
||||||
let playableDuration:Float64 = CMTimeGetSeconds(CMTimeRangeGetEnd(effectiveTimeRange))
|
let playableDuration:Float64 = CMTimeGetSeconds(CMTimeRangeGetEnd(effectiveTimeRange))
|
||||||
if playableDuration > 0 {
|
if playableDuration > 0 {
|
||||||
|
if (source?.startTime != nil) {
|
||||||
|
return NSNumber(value: (playableDuration - Float64(source?.startTime ?? 0) / 1000))
|
||||||
|
}
|
||||||
|
|
||||||
return playableDuration as NSNumber
|
return playableDuration as NSNumber
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
#import <React/RCTViewManager.h>
|
#import <React/RCTViewManager.h>
|
||||||
#import "RCTVideoSwiftLog.h"
|
#import "RCTVideoSwiftLog.h"
|
||||||
|
#import "RCTEventDispatcher.h"
|
||||||
|
|
||||||
#if __has_include(<react-native-video/RCTVideoCache.h>)
|
#if __has_include(<react-native-video/RCTVideoCache.h>)
|
||||||
#import "RCTVideoCache.h"
|
#import "RCTVideoCache.h"
|
||||||
|
@ -60,6 +60,7 @@ class RCTVideo: UIView, RCTVideoPlayerViewControllerDelegate, RCTPlayerObserverH
|
|||||||
private var _fullscreenAutorotate:Bool = true
|
private var _fullscreenAutorotate:Bool = true
|
||||||
private var _fullscreenOrientation:String! = "all"
|
private var _fullscreenOrientation:String! = "all"
|
||||||
private var _fullscreenPlayerPresented:Bool = false
|
private var _fullscreenPlayerPresented:Bool = false
|
||||||
|
private var _fullscreenUncontrolPlayerPresented:Bool = false // to call events switching full screen mode from player controls
|
||||||
private var _filterName:String!
|
private var _filterName:String!
|
||||||
private var _filterEnabled:Bool = false
|
private var _filterEnabled:Bool = false
|
||||||
private var _presentingViewController:UIViewController?
|
private var _presentingViewController:UIViewController?
|
||||||
@ -214,7 +215,10 @@ class RCTVideo: UIView, RCTVideoPlayerViewControllerDelegate, RCTPlayerObserverH
|
|||||||
return
|
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 currentPlaybackTime = _player?.currentItem?.currentDate()
|
||||||
let duration = CMTimeGetSeconds(playerDuration)
|
let duration = CMTimeGetSeconds(playerDuration)
|
||||||
let currentTimeSecs = CMTimeGetSeconds(currentTime ?? .zero)
|
let currentTimeSecs = CMTimeGetSeconds(currentTime ?? .zero)
|
||||||
@ -232,7 +236,7 @@ class RCTVideo: UIView, RCTVideoPlayerViewControllerDelegate, RCTPlayerObserverH
|
|||||||
#endif
|
#endif
|
||||||
onVideoProgress?([
|
onVideoProgress?([
|
||||||
"currentTime": NSNumber(value: Float(currentTimeSecs)),
|
"currentTime": NSNumber(value: Float(currentTimeSecs)),
|
||||||
"playableDuration": RCTVideoUtils.calculatePlayableDuration(_player),
|
"playableDuration": RCTVideoUtils.calculatePlayableDuration(_player, withSource: _source),
|
||||||
"atValue": NSNumber(value: currentTime?.value ?? .zero),
|
"atValue": NSNumber(value: currentTime?.value ?? .zero),
|
||||||
"currentPlaybackTime": NSNumber(value: NSNumber(value: floor(currentPlaybackTime?.timeIntervalSince1970 ?? 0 * 1000)).int64Value),
|
"currentPlaybackTime": NSNumber(value: NSNumber(value: floor(currentPlaybackTime?.timeIntervalSince1970 ?? 0 * 1000)).int64Value),
|
||||||
"target": reactTag,
|
"target": reactTag,
|
||||||
@ -244,7 +248,8 @@ class RCTVideo: UIView, RCTVideoPlayerViewControllerDelegate, RCTPlayerObserverH
|
|||||||
// MARK: - Player and source
|
// MARK: - Player and source
|
||||||
@objc
|
@objc
|
||||||
func setSrc(_ source:NSDictionary!) {
|
func setSrc(_ source:NSDictionary!) {
|
||||||
DispatchQueue.global(qos: .default).async {
|
DispatchQueue.global(qos: .default).async { [weak self] in
|
||||||
|
guard let self = self else {return}
|
||||||
self._source = VideoSource(source)
|
self._source = VideoSource(source)
|
||||||
if (self._source?.uri == nil || self._source?.uri == "") {
|
if (self._source?.uri == nil || self._source?.uri == "") {
|
||||||
self._player?.replaceCurrentItem(with: nil)
|
self._player?.replaceCurrentItem(with: nil)
|
||||||
@ -301,6 +306,7 @@ class RCTVideo: UIView, RCTVideoPlayerViewControllerDelegate, RCTPlayerObserverH
|
|||||||
self._playerItem = playerItem
|
self._playerItem = playerItem
|
||||||
self._playerObserver.playerItem = self._playerItem
|
self._playerObserver.playerItem = self._playerItem
|
||||||
self.setPreferredForwardBufferDuration(self._preferredForwardBufferDuration)
|
self.setPreferredForwardBufferDuration(self._preferredForwardBufferDuration)
|
||||||
|
self.setPlaybackRange(playerItem, withVideoStart: self._source?.startTime, withVideoEnd: self._source?.endTime)
|
||||||
self.setFilter(self._filterName)
|
self.setFilter(self._filterName)
|
||||||
if let maxBitRate = self._maxBitRate {
|
if let maxBitRate = self._maxBitRate {
|
||||||
self._playerItem?.preferredPeakBitRate = Double(maxBitRate)
|
self._playerItem?.preferredPeakBitRate = Double(maxBitRate)
|
||||||
@ -462,15 +468,6 @@ class RCTVideo: UIView, RCTVideoPlayerViewControllerDelegate, RCTPlayerObserverH
|
|||||||
_paused = paused
|
_paused = paused
|
||||||
}
|
}
|
||||||
|
|
||||||
@objc
|
|
||||||
func setCurrentTime(_ currentTime:Float) {
|
|
||||||
let info:NSDictionary = [
|
|
||||||
"time": NSNumber(value: currentTime),
|
|
||||||
"tolerance": NSNumber(value: 100)
|
|
||||||
]
|
|
||||||
setSeek(info)
|
|
||||||
}
|
|
||||||
|
|
||||||
@objc
|
@objc
|
||||||
func setSeek(_ info:NSDictionary!) {
|
func setSeek(_ info:NSDictionary!) {
|
||||||
let seekTime:NSNumber! = info["time"] as! NSNumber
|
let seekTime:NSNumber! = info["time"] as! NSNumber
|
||||||
@ -510,6 +507,11 @@ class RCTVideo: UIView, RCTVideoPlayerViewControllerDelegate, RCTPlayerObserverH
|
|||||||
applyModifiers()
|
applyModifiers()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@objc
|
||||||
|
func isMuted() -> Bool {
|
||||||
|
return _muted
|
||||||
|
}
|
||||||
|
|
||||||
@objc
|
@objc
|
||||||
func setMuted(_ muted:Bool) {
|
func setMuted(_ muted:Bool) {
|
||||||
_muted = muted
|
_muted = muted
|
||||||
@ -561,6 +563,18 @@ class RCTVideo: UIView, RCTVideoPlayerViewControllerDelegate, RCTPlayerObserverH
|
|||||||
// Fallback on earlier versions
|
// Fallback on earlier versions
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func setPlaybackRange(_ item:AVPlayerItem!, withVideoStart videoStart:Int64?, withVideoEnd videoEnd:Int64?) {
|
||||||
|
if (videoStart != nil) {
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
func applyModifiers() {
|
func applyModifiers() {
|
||||||
@ -666,7 +680,13 @@ class RCTVideo: UIView, RCTVideoPlayerViewControllerDelegate, RCTPlayerObserverH
|
|||||||
self.onVideoFullscreenPlayerWillPresent?(["target": reactTag as Any])
|
self.onVideoFullscreenPlayerWillPresent?(["target": reactTag as Any])
|
||||||
|
|
||||||
if let playerViewController = _playerViewController {
|
if let playerViewController = _playerViewController {
|
||||||
viewController.present(playerViewController, animated:true, completion:{
|
if(_controls) {
|
||||||
|
// prevents crash https://github.com/react-native-video/react-native-video/issues/3040
|
||||||
|
self._playerViewController?.removeFromParent()
|
||||||
|
}
|
||||||
|
|
||||||
|
viewController.present(playerViewController, animated:true, completion:{ [weak self] in
|
||||||
|
guard let self = self else {return}
|
||||||
self._playerViewController?.showsPlaybackControls = self._controls
|
self._playerViewController?.showsPlaybackControls = self._controls
|
||||||
self._fullscreenPlayerPresented = fullscreen
|
self._fullscreenPlayerPresented = fullscreen
|
||||||
self._playerViewController?.autorotate = self._fullscreenAutorotate
|
self._playerViewController?.autorotate = self._fullscreenAutorotate
|
||||||
@ -678,8 +698,8 @@ class RCTVideo: UIView, RCTVideoPlayerViewControllerDelegate, RCTPlayerObserverH
|
|||||||
}
|
}
|
||||||
} else if !fullscreen && _fullscreenPlayerPresented, let _playerViewController = _playerViewController {
|
} else if !fullscreen && _fullscreenPlayerPresented, let _playerViewController = _playerViewController {
|
||||||
self.videoPlayerViewControllerWillDismiss(playerViewController: _playerViewController)
|
self.videoPlayerViewControllerWillDismiss(playerViewController: _playerViewController)
|
||||||
_presentingViewController?.dismiss(animated: true, completion:{
|
_presentingViewController?.dismiss(animated: true, completion:{[weak self] in
|
||||||
self.videoPlayerViewControllerDidDismiss(playerViewController: _playerViewController)
|
self?.videoPlayerViewControllerDidDismiss(playerViewController: _playerViewController)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1025,7 +1045,10 @@ class RCTVideo: UIView, RCTVideoPlayerViewControllerDelegate, RCTPlayerObserverH
|
|||||||
}
|
}
|
||||||
|
|
||||||
if _pendingSeek {
|
if _pendingSeek {
|
||||||
setCurrentTime(_pendingSeekTime)
|
setSeek([
|
||||||
|
"time": NSNumber(value: _pendingSeekTime),
|
||||||
|
"tolerance": NSNumber(value: 100)
|
||||||
|
])
|
||||||
_pendingSeek = false
|
_pendingSeek = false
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1104,12 +1127,27 @@ class RCTVideo: UIView, RCTVideoPlayerViewControllerDelegate, RCTPlayerObserverH
|
|||||||
let oldRect = change.oldValue
|
let oldRect = change.oldValue
|
||||||
let newRect = change.newValue
|
let newRect = change.newValue
|
||||||
if !oldRect!.equalTo(newRect!) {
|
if !oldRect!.equalTo(newRect!) {
|
||||||
|
// https://github.com/react-native-video/react-native-video/issues/3085#issuecomment-1557293391
|
||||||
if newRect!.equalTo(UIScreen.main.bounds) {
|
if newRect!.equalTo(UIScreen.main.bounds) {
|
||||||
RCTLog("in fullscreen")
|
RCTLog("in fullscreen")
|
||||||
|
if (!_fullscreenUncontrolPlayerPresented) {
|
||||||
|
_fullscreenUncontrolPlayerPresented = true;
|
||||||
|
|
||||||
self.reactViewController().view.frame = UIScreen.main.bounds
|
self.onVideoFullscreenPlayerWillPresent?(["target": self.reactTag as Any])
|
||||||
self.reactViewController().view.setNeedsLayout()
|
self.onVideoFullscreenPlayerDidPresent?(["target": self.reactTag as Any])
|
||||||
} else {NSLog("not fullscreen")}
|
}
|
||||||
|
} else {
|
||||||
|
NSLog("not fullscreen")
|
||||||
|
if (_fullscreenUncontrolPlayerPresented) {
|
||||||
|
_fullscreenUncontrolPlayerPresented = false;
|
||||||
|
|
||||||
|
self.onVideoFullscreenPlayerWillDismiss?(["target": self.reactTag as Any])
|
||||||
|
self.onVideoFullscreenPlayerDidDismiss?(["target": self.reactTag as Any])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
self.reactViewController().view.frame = UIScreen.main.bounds
|
||||||
|
self.reactViewController().view.setNeedsLayout()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -28,7 +28,6 @@ RCT_EXPORT_VIEW_PROPERTY(ignoreSilentSwitch, NSString);
|
|||||||
RCT_EXPORT_VIEW_PROPERTY(mixWithOthers, NSString);
|
RCT_EXPORT_VIEW_PROPERTY(mixWithOthers, NSString);
|
||||||
RCT_EXPORT_VIEW_PROPERTY(rate, float);
|
RCT_EXPORT_VIEW_PROPERTY(rate, float);
|
||||||
RCT_EXPORT_VIEW_PROPERTY(seek, NSDictionary);
|
RCT_EXPORT_VIEW_PROPERTY(seek, NSDictionary);
|
||||||
RCT_EXPORT_VIEW_PROPERTY(currentTime, float);
|
|
||||||
RCT_EXPORT_VIEW_PROPERTY(fullscreen, BOOL);
|
RCT_EXPORT_VIEW_PROPERTY(fullscreen, BOOL);
|
||||||
RCT_EXPORT_VIEW_PROPERTY(fullscreenAutorotate, BOOL);
|
RCT_EXPORT_VIEW_PROPERTY(fullscreenAutorotate, BOOL);
|
||||||
RCT_EXPORT_VIEW_PROPERTY(fullscreenOrientation, NSString);
|
RCT_EXPORT_VIEW_PROPERTY(fullscreenOrientation, NSString);
|
||||||
|
@ -2,7 +2,7 @@ import AVKit
|
|||||||
|
|
||||||
class RCTVideoPlayerViewController: AVPlayerViewController {
|
class RCTVideoPlayerViewController: AVPlayerViewController {
|
||||||
|
|
||||||
var rctDelegate:RCTVideoPlayerViewControllerDelegate!
|
weak var rctDelegate: RCTVideoPlayerViewControllerDelegate?
|
||||||
|
|
||||||
// Optional paramters
|
// Optional paramters
|
||||||
var preferredOrientation:String?
|
var preferredOrientation:String?
|
||||||
@ -19,11 +19,9 @@ class RCTVideoPlayerViewController: AVPlayerViewController {
|
|||||||
|
|
||||||
override func viewDidDisappear(_ animated: Bool) {
|
override func viewDidDisappear(_ animated: Bool) {
|
||||||
super.viewDidDisappear(animated)
|
super.viewDidDisappear(animated)
|
||||||
|
|
||||||
if rctDelegate != nil {
|
rctDelegate?.videoPlayerViewControllerWillDismiss(playerViewController: self)
|
||||||
rctDelegate.videoPlayerViewControllerWillDismiss(playerViewController: self)
|
rctDelegate?.videoPlayerViewControllerDidDismiss(playerViewController: self)
|
||||||
rctDelegate.videoPlayerViewControllerDidDismiss(playerViewController: self)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#if !TARGET_OS_TV
|
#if !TARGET_OS_TV
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "react-native-video",
|
"name": "react-native-video",
|
||||||
"version": "6.0.0-alpha.5",
|
"version": "6.0.0-alpha.6",
|
||||||
"description": "A <Video /> element for react-native",
|
"description": "A <Video /> element for react-native",
|
||||||
"main": "Video.js",
|
"main": "Video.js",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
@ -24,7 +24,8 @@
|
|||||||
},
|
},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"lint": "yarn eslint .",
|
"lint": "yarn eslint .",
|
||||||
"xbasic": "yarn --cwd examples/basic"
|
"xbasic": "yarn --cwd examples/basic",
|
||||||
|
"test": "echo no test available"
|
||||||
},
|
},
|
||||||
"files": [
|
"files": [
|
||||||
"android",
|
"android",
|
||||||
|
Loading…
Reference in New Issue
Block a user