Merge branch 'master' of https://github.com/react-native-video/react-native-video into fix/report_time_position_when_updated

# Conflicts:
#	CHANGELOG.md
This commit is contained in:
olivier bouillet 2022-06-23 21:58:11 +02:00
commit 5aeb49e094
41 changed files with 148 additions and 2472 deletions

View File

@ -10,8 +10,7 @@ Describe what you wanted to happen
### Platform
Which player are you experiencing the problem on:
* iOS
* Android ExoPlayer
* Android MediaPlayer
* Android
* Windows UWP
* Windows WPF

View File

@ -18,12 +18,11 @@ assignees: ''
## Platform
<!--
Platform where your bug is happening. If Android, report if using Android or Android Exoplayer
Platform where your bug is happening.
-->
Which player are you experiencing the problem on:
* iOS
* Android ExoPlayer
* Android MediaPlayer
* Android
* Windows UWP
* Windows WPF

211
API.md
View File

@ -5,7 +5,6 @@
* [tvOS](#tvos-installation)
* [Android](#android-installation)
* [Windows](#windows-installation)
* [react-native-dom](#react-native-dom-installation)
* [Examples](#examples)
* [iOS](#ios-example)
* [Android](#android-example)
@ -111,14 +110,7 @@ Or if you have trouble, make the following additions to the given files manually
#### **android/settings.gradle**
The newer ExoPlayer library will work for most people.
```gradle
include ':react-native-video'
project(':react-native-video').projectDir = new File(rootProject.projectDir, '../node_modules/react-native-video/android-exoplayer')
```
If you need to use the old Android MediaPlayer based player, use the following instead:
Add player source in build configuration
```gradle
include ':react-native-video'
@ -220,32 +212,6 @@ Follow the manual linking instuctions for React Native Windows 0.62 above, but s
</details>
### react-native-dom installation
<details>
<summary>react-native-dom details</summary>
Make the following additions to the given files manually:
#### **dom/bootstrap.js**
Import RCTVideoManager and add it to the list of nativeModules:
```javascript
import { RNDomInstance } from "react-native-dom";
import { name as appName } from "../app.json";
import RCTVideoManager from 'react-native-video/dom/RCTVideoManager'; // Add this
// Path to RN Bundle Entrypoint ================================================
const rnBundlePath = "./entry.bundle?platform=dom&dev=true";
// React Native DOM Runtime Options =============================================
const ReactNativeDomOptions = {
enableHotReload: false,
nativeModules: [RCTVideoManager] // Add this
};
```
</details>
## Examples
Run `yarn xbasic install` before running any of the examples.
@ -302,29 +268,28 @@ var styles = StyleSheet.create({
|[allowsExternalPlayback](#allowsexternalplayback) |iOS |
|[audioOnly](#audioonly)|All |
|[automaticallyWaitsToMinimizeStalling](#automaticallyWaitsToMinimizeStalling) | iOS|\
|[backBufferDurationMs](#backBufferDurationMs)| Android Exoplayer|
|[bufferConfig](#bufferconfig)|Android ExoPlayer|
|[contentStartTime](#contentStartTime)| Android Exoplayer|
|[controls](#controls)|Android ExoPlayer, iOS, react-native-dom|
|[currentPlaybackTime](#currentPlaybackTime)|Android Exoplayer|
|[disableFocus](#disableFocus)|Android Exoplayer, iOS|
|[disableDisconnectError](#disableDisconnectError)|Android Exoplayer|
|[backBufferDurationMs](#backBufferDurationMs)| Android |
|[bufferConfig](#bufferconfig)|Android|
|[contentStartTime](#contentStartTime)| Android |
|[controls](#controls)|Android, iOS|
|[currentPlaybackTime](#currentPlaybackTime)|Android|
|[disableFocus](#disableFocus)|Android, iOS|
|[disableDisconnectError](#disableDisconnectError)|Android|
|[filter](#filter)|iOS|
|[filterEnabled](#filterEnabled)|iOS|
|[fullscreen](#fullscreen)|iOS|
|[fullscreenAutorotate](#fullscreenautorotate)|iOS|
|[fullscreenOrientation](#fullscreenorientation)|iOS|
|[headers](#headers)|Android ExoPlayer|
|[hideShutterView](#hideshutterview)|Android ExoPlayer|
|[id](#id)|react-native-dom|
|[headers](#headers)|Android|
|[hideShutterView](#hideshutterview)|Android|
|[ignoreSilentSwitch](#ignoresilentswitch)|iOS|
|[maxBitRate](#maxbitrate)|Android ExoPlayer, iOS|
|[minLoadRetryCount](#minLoadRetryCount)|Android ExoPlayer|
|[maxBitRate](#maxbitrate)|Android, iOS|
|[minLoadRetryCount](#minLoadRetryCount)|Android|
|[mixWithOthers](#mixWithOthers)|iOS|
|[muted](#muted)|All|
|[paused](#paused)|All|
|[pictureInPicture](#pictureinpicture)|iOS|
|[playInBackground](#playinbackground)|Android ExoPlayer, Android MediaPlayer, iOS|
|[playInBackground](#playinbackground)|Android, iOS|
|[playWhenInactive](#playwheninactive)|iOS|
|[poster](#poster)|All|
|[posterResizeMode](#posterresizemode)|All|
@ -333,17 +298,16 @@ var styles = StyleSheet.create({
|[progressUpdateInterval](#progressupdateinterval)|All|
|[rate](#rate)|All|
|[repeat](#repeat)|All|
|[reportBandwidth](#reportbandwidth)|Android ExoPlayer|
|[resizeMode](#resizemode)|Android ExoPlayer, Android MediaPlayer, iOS, Windows UWP|
|[selectedAudioTrack](#selectedaudiotrack)|Android ExoPlayer, iOS|
|[selectedTextTrack](#selectedtexttrack)|Android ExoPlayer, iOS|
|[selectedVideoTrack](#selectedvideotrack)|Android ExoPlayer|
|[reportBandwidth](#reportbandwidth)|Android|
|[resizeMode](#resizemode)|Android, iOS, Windows UWP|
|[selectedAudioTrack](#selectedaudiotrack)|Android, iOS|
|[selectedTextTrack](#selectedtexttrack)|Android, iOS|
|[selectedVideoTrack](#selectedvideotrack)|Android|
|[source](#source)|All|
|[stereoPan](#stereopan)|Android MediaPlayer|
|[textTracks](#texttracks)|Android ExoPlayer, iOS|
|[trackId](#trackId)|Android ExoPlayer|
|[useTextureView](#usetextureview)|Android ExoPlayer|
|[useSecureView](#useSecureView)|Android Exoplayer|
|[textTracks](#texttracks)|Android, iOS|
|[trackId](#trackId)|Android|
|[useTextureView](#usetextureview)|Android|
|[useSecureView](#useSecureView)|Android|
|[volume](#volume)|All|
|[localSourceEncryptionKeyScheme](#localSourceEncryptionKeyScheme)|All|
@ -351,31 +315,31 @@ var styles = StyleSheet.create({
### Event props
| Name |Plateforms Support |
|--|--|
|[onAudioBecomingNoisy](#onaudiobecomingnoisy)|Android ExoPlayer, iOS|
|[onBandwidthUpdate](#onbandwidthupdate)|Android ExoPlayer|
|[onBuffer](#onbuffer)|Android ExoPlayer, iOS|
|[onAudioBecomingNoisy](#onaudiobecomingnoisy)|Android, iOS|
|[onBandwidthUpdate](#onbandwidthupdate)|Android|
|[onBuffer](#onbuffer)|Android, iOS|
|[onEnd](#onend)|All|
|[onExternalPlaybackChange](#onexternalplaybackchange)|iOS|
|[onFullscreenPlayerWillPresent](#onfullscreenplayerwillpresent)|Android ExoPlayer, Android MediaPlayer, iOS|
|[onFullscreenPlayerDidPresent](#onfullscreenplayerdidpresent)|Android ExoPlayer, Android MediaPlayer, iOS|
|[onFullscreenPlayerWillDismiss](#onfullscreenplayerwilldismiss)|Android ExoPlayer, Android MediaPlayer, iOS|
|[onFullscreenPlayerDidDismiss](#onfullscreenplayerdiddismiss)|Android ExoPlayer, Android MediaPlayer, iOS|
|[onFullscreenPlayerWillPresent](#onfullscreenplayerwillpresent)|Android, iOS|
|[onFullscreenPlayerDidPresent](#onfullscreenplayerdidpresent)|Android, iOS|
|[onFullscreenPlayerWillDismiss](#onfullscreenplayerwilldismiss)|Android, iOS|
|[onFullscreenPlayerDidDismiss](#onfullscreenplayerdiddismiss)|Android, iOS|
|[onLoad](#onload)|All|
|[onLoadStart](#onloadstart)|All|
|[onReadyForDisplay](#onreadyfordisplay)|Android ExoPlayer, Android MediaPlayer, iOS, Web|
|[onReadyForDisplay](#onreadyfordisplay)|Android, iOS, Web|
|[onPictureInPictureStatusChanged](#onpictureinpicturestatuschanged)|iOS|
|[onPlaybackRateChange](#onplaybackratechange)|All|
|[onProgress](#onprogress)|All|
|[onSeek](#onseek)|Android ExoPlayer, Android MediaPlayer, iOS, Windows UWP|
|[onSeek](#onseek)|Android, iOS, Windows UWP|
|[onRestoreUserInterfaceForPictureInPictureStop](#onrestoreuserinterfaceforpictureinpicturestop)|iOS|
|[onTimedMetadata](#ontimedmetadata)|Android ExoPlayer, Android MediaPlayer, iOS|
|[onTimedMetadata](#ontimedmetadata)|Android, iOS|
### Methods
| Name |Plateforms Support |
|--|--|
|[dismissFullscreenPlayer](#dismissfullscreenplayer)|Android ExoPlayer, Android MediaPlayer, iOS|
|[presentFullscreenPlayer](#presentfullscreenplayer)|Android ExoPlayer, Android MediaPlayer, iOS|
|[dismissFullscreenPlayer](#dismissfullscreenplayer)|Android, iOS|
|[presentFullscreenPlayer](#presentfullscreenplayer)|Android, iOS|
|[save](#save)|iOS|
|[restoreUserInterfaceForPictureInPictureStop](#restoreuserinterfaceforpictureinpicturestop)|iOS|
|[seek](#seek)|All|
@ -409,7 +373,7 @@ Platforms: iOS
#### backBufferDurationMs
The number of milliseconds of buffer to keep before the current position. This allows rewinding without rebuffering within that duration.
Platforms: Android ExoPlayer
Platforms: Android
#### bufferConfig
Adjust the buffer settings. This prop takes an object with one or more of the properties listed below.
@ -436,12 +400,12 @@ bufferConfig={{
}}
```
Platforms: Android ExoPlayer
Platforms: Android
#### currentPlaybackTime
When playing an HLS live stream with a `EXT-X-PROGRAM-DATE-TIME` tag configured, then this property will contain the epoch value in msec.
Platforms: Android ExoPlayer, iOS
Platforms: Android, iOS
#### controls
Determines whether to show player controls.
@ -453,30 +417,28 @@ Note on iOS, controls are always shown when in fullscreen mode.
### contentStartTime
The start time in ms for SSAI content. This determines at what time to load the video info like resolutions. Use this only when you have SSAI stream where ads resolution is not the same as content resolution.
For Android MediaPlayer, you will need to build your own controls or use a package like [react-native-video-controls](https://github.com/itsnubix/react-native-video-controls) or [react-native-video-player](https://github.com/cornedor/react-native-video-player).
Note on Android, native controls are available by default. If needed, you can also add your controls or use a package like [react-native-video-controls].
Note on Android ExoPlayer, native controls are available by default. If needed, you can also add your controls or use a package like [react-native-video-controls].
Platforms: Android ExoPlayer, iOS, react-native-dom
Platforms: Android, iOS
#### disableFocus
Determines whether video audio should override background music/audio in Android devices.
* ** false (default)** - Override background audio/music
* **true** - Let background audio/music from other apps play
Platforms: Android Exoplayer
Platforms: Android
#### disableDisconnectError
Determines if the player needs to throw an error when connection is lost or not
* **false (default)** - Player will throw an error when connection is lost
* **true** - Player will keep trying to buffer when network connect is lost
Platforms: Android Exoplayer
Platforms: Android
### DRM
To setup DRM please follow [this guide](./DRM.md)
Platforms: Android Exoplayer, iOS
Platforms: Android, iOS
#### filter
Add video filter
@ -548,7 +510,7 @@ source={{
}}
```
Platforms: Android ExoPlayer
Platforms: Android
#### hideShutterView
Controls whether the ExoPlayer shutter view (black screen while loading) is enabled.
@ -556,17 +518,7 @@ Controls whether the ExoPlayer shutter view (black screen while loading) is enab
* **false (default)** - Show shutter view
* **true** - Hide shutter view
Platforms: Android ExoPlayer
#### id
Set the DOM id element so you can use document.getElementById on web platforms. Accepts string values.
Example:
```
id="video"
```
Platforms: react-native-dom
Platforms: Android
#### ignoreSilentSwitch
Controls the iOS silent switch behavior
@ -586,7 +538,7 @@ Example:
maxBitRate={2000000} // 2 megabits
```
Platforms: Android ExoPlayer, iOS
Platforms: Android, iOS
#### minLoadRetryCount
Sets the minimum number of times to retry loading data before failing and reporting an error to the application. Useful to recover from transient internet failures.
@ -598,7 +550,7 @@ Example:
minLoadRetryCount={5} // retry 5 times
```
Platforms: Android ExoPlayer
Platforms: Android
#### mixWithOthers
Controls how Audio mix with other apps.
@ -638,7 +590,7 @@ To use this feature on iOS, you must:
* [Enable Background Audio](https://developer.apple.com/library/archive/documentation/Audio/Conceptual/AudioSessionProgrammingGuide/AudioSessionBasics/AudioSessionBasics.html#//apple_ref/doc/uid/TP40007875-CH3-SW3) in your Xcode project
* Set the ignoreSilentSwitch prop to "ignore"
Platforms: Android ExoPlayer, Android MediaPlayer, iOS
Platforms: Android, iOS
#### playWhenInactive
Determine whether the media should continue playing when notifications or the Control Center are in front of the video.
@ -693,8 +645,6 @@ Speed at which the media should play.
Platforms: all
Note: For Android MediaPlayer, rate is only supported on Android 6.0 and higher devices.
#### repeat
Determine whether to repeat the video when the end is reached
* **false (default)** - Don't repeat the video
@ -708,7 +658,7 @@ Determine whether to generate onBandwidthUpdate events. This is needed due to th
* **false (default)** - Don't generate onBandwidthUpdate events
* **true** - Generate onBandwidthUpdate events
Platforms: Android ExoPlayer
Platforms: Android
#### resizeMode
Determines how to resize the video when the frame doesn't match the raw video dimensions.
@ -717,7 +667,7 @@ Determines how to resize the video when the frame doesn't match the raw video di
* **"cover"** - Scale the video uniformly (maintain the video's aspect ratio) so that both dimensions (width and height) of the image will be equal to or larger than the corresponding dimension of the view (minus padding).
* **"stretch"** - Scale width and height independently, This may change the aspect ratio of the src.
Platforms: Android ExoPlayer, Android MediaPlayer, iOS, Windows UWP
Platforms: Android, iOS, Windows UWP
#### selectedAudioTrack
Configure which audio track, if any, is played.
@ -747,7 +697,7 @@ Type | Value | Description
If a track matching the specified Type (and Value if appropriate) is unavailable, the first audio track will be played. If multiple tracks match the criteria, the first match will be used.
Platforms: Android ExoPlayer, iOS
Platforms: Android, iOS
#### selectedTextTrack
Configure which text track (caption or subtitle), if any, is shown.
@ -779,7 +729,7 @@ Both iOS & Android (only 4.4 and higher) offer Settings to enable Captions for h
If a track matching the specified Type (and Value if appropriate) is unavailable, no text track will be displayed. If multiple tracks match the criteria, the first match will be used.
Platforms: Android ExoPlayer, iOS
Platforms: Android, iOS
#### selectedVideoTrack
Configure which video track should be played. By default, the player uses Adaptive Bitrate Streaming to automatically select the stream it thinks will perform best based on available bandwidth.
@ -808,7 +758,7 @@ Type | Value | Description
If a track matching the specified Type (and Value if appropriate) is unavailable, ABR will be used.
Platforms: Android ExoPlayer
Platforms: Android
#### source
Sets the media source. You can pass an asset loaded via require or an object with a uri.
@ -853,7 +803,7 @@ source={{ uri: 'file:///sdcard/Movies/sintel.mp4' }}
Note: Your app will need to request permission to read external storage if you're accessing a file outside your app.
Platforms: Android ExoPlayer, Android MediaPlayer, possibly others
Platforms: Android, possibly others
###### iPod Library (ipod-library://)
@ -885,14 +835,6 @@ The following other types are supported on some platforms, but aren't fully docu
`content://, ms-appx://, ms-appdata://, assets-library://`
#### stereoPan
Adjust the balance of the left and right audio channels. Any value between 1.0 and 1.0 is accepted.
* **-1.0** - Full left
* **0.0 (default)** - Center
* **1.0** - Full right
Platforms: Android MediaPlayer
#### textTracks
Load one or more "sidecar" text tracks. This takes an array of objects representing each track. Each object should have the format:
@ -900,7 +842,7 @@ Property | Description
--- | ---
title | Descriptive name for the track
language | 2 letter [ISO 639-1 code](https://en.wikipedia.org/wiki/List_of_ISO_639-1_codes) representing the language
type | Mime type of the track<br> * TextTrackType.SRT - SubRip (.srt)<br> * TextTrackType.TTML - TTML (.ttml)<br> * TextTrackType.VTT - WebVTT (.vtt)<br>iOS only supports VTT, Android ExoPlayer supports all 3
type | Mime type of the track<br> * TextTrackType.SRT - SubRip (.srt)<br> * TextTrackType.TTML - TTML (.ttml)<br> * TextTrackType.VTT - WebVTT (.vtt)<br>iOS only supports VTT, Android supports all 3
uri | URL for the text track. Currently, only tracks hosted on a webserver are supported
On iOS, sidecar text tracks are only supported for individual files, not HLS playlists. For HLS, you should include the text tracks as part of the playlist.
@ -928,12 +870,12 @@ textTracks={[
```
Platforms: Android ExoPlayer, iOS
Platforms: Android, iOS
#### trackId
Configure an identifier for the video stream to link the playback context to the events emitted.
Platforms: Android ExoPlayer
Platforms: Android
#### useTextureView
Controls whether to output to a TextureView or SurfaceView.
@ -947,7 +889,7 @@ useTextureView can only be set at same time you're setting the source.
* **true (default)** - Use a TextureView
* **false** - Use a SurfaceView
Platforms: Android ExoPlayer
Platforms: Android
#### useSecureView
Force the output to a SurfaceView and enables the secure surface.
@ -959,7 +901,7 @@ SurfaceView is is the only one that can be labeled as secure.
* **true** - Use security
* **false (default)** - Do not use security
Platforms: Android ExoPlayer
Platforms: Android
#### volume
Adjust the volume.
@ -989,7 +931,7 @@ Callback function that is called when the audio is about to become 'noisy' due t
Payload: none
Platforms: Android ExoPlayer, iOS
Platforms: Android, iOS
#### onBandwidthUpdate
Callback function that is called when the available bandwidth changes.
@ -1007,9 +949,9 @@ Example:
}
```
Note: On Android ExoPlayer, you must set the [reportBandwidth](#reportbandwidth) prop to enable this event. This is due to the high volume of events generated.
Note: On Android, you must set the [reportBandwidth](#reportbandwidth) prop to enable this event. This is due to the high volume of events generated.
Platforms: Android ExoPlayer
Platforms: Android
#### onBuffer
Callback function that is called when the player buffers.
@ -1027,7 +969,7 @@ Example:
}
```
Platforms: Android ExoPlayer, iOS
Platforms: Android, iOS
#### onEnd
Callback function that is called when the player reaches the end of the media.
@ -1059,28 +1001,28 @@ Callback function that is called when the player is about to enter fullscreen mo
Payload: none
Platforms: Android ExoPlayer, Android MediaPlayer, iOS
Platforms: Android, iOS
#### onFullscreenPlayerDidPresent
Callback function that is called when the player has entered fullscreen mode.
Payload: none
Platforms: Android ExoPlayer, Android MediaPlayer, iOS
Platforms: Android, iOS
#### onFullscreenPlayerWillDismiss
Callback function that is called when the player is about to exit fullscreen mode.
Payload: none
Platforms: Android ExoPlayer, Android MediaPlayer, iOS
Platforms: Android, iOS
#### onFullscreenPlayerDidDismiss
Callback function that is called when the player has exited fullscreen mode.
Payload: none
Platforms: Android ExoPlayer, Android MediaPlayer, iOS
Platforms: Android, iOS
#### onLoad
Callback function that is called when the media is loaded and ready to play.
@ -1169,7 +1111,7 @@ Example:
}
```
Platforms: Android ExoPlayer
Platforms: Android
#### onReadyForDisplay
Callback function that is called when the first video frame is ready for display. This is when the poster is removed.
@ -1177,10 +1119,9 @@ Callback function that is called when the first video frame is ready for display
Payload: none
* iOS: [readyForDisplay](https://developer.apple.com/documentation/avkit/avplayerviewcontroller/1615830-readyfordisplay?language=objc)
* Android: [MEDIA_INFO_VIDEO_RENDERING_START](https://developer.android.com/reference/android/media/MediaPlayer#MEDIA_INFO_VIDEO_RENDERING_START)
* Android ExoPlayer [STATE_READY](https://exoplayer.dev/doc/reference/com/google/android/exoplayer2/Player.html#STATE_READY)
* Android [STATE_READY](https://exoplayer.dev/doc/reference/com/google/android/exoplayer2/Player.html#STATE_READY)
Platforms: Android ExoPlayer, Android MediaPlayer, iOS, Web
Platforms: Android, iOS, Web
#### onPictureInPictureStatusChanged
Callback function that is called when picture in picture becomes active or inactive.
@ -1256,7 +1197,7 @@ Example:
Both the currentTime & seekTime are reported because the video player may not seek to the exact requested position in order to improve seek performance.
Platforms: Android ExoPlayer, Android MediaPlayer, iOS, Windows UWP
Platforms: Android, iOS, Windows UWP
#### onRestoreUserInterfaceForPictureInPictureStop
Callback function that corresponds to Apple's [`restoreUserInterfaceForPictureInPictureStopWithCompletionHandler`](https://developer.apple.com/documentation/avkit/avpictureinpicturecontrollerdelegate/1614703-pictureinpicturecontroller?language=objc). Call `restoreUserInterfaceForPictureInPictureStopCompleted` inside of this function when done restoring the user interface.
@ -1285,9 +1226,7 @@ Example:
}
```
Support for timed metadata on Android MediaPlayer is limited at best and only compatible with some videos. It requires a target SDK of 23 or higher.
Platforms: Android ExoPlayer, Android MediaPlayer, iOS
Platforms: Android, iOS
### Methods
Methods operate on a ref to the Video element. You can create a ref using code like:
@ -1308,7 +1247,7 @@ Example:
this.player.dismissFullscreenPlayer();
```
Platforms: Android ExoPlayer, Android MediaPlayer, iOS
Platforms: Android, iOS
#### presentFullscreenPlayer
`presentFullscreenPlayer()`
@ -1317,14 +1256,14 @@ Put the player in fullscreen mode.
On iOS, this displays the video in a fullscreen view controller with controls.
On Android ExoPlayer & MediaPlayer, this puts the navigation controls in fullscreen mode. It is not a complete fullscreen implementation, so you will still need to apply a style that makes the width and height match your screen dimensions to get a fullscreen video.
On Android, this puts the navigation controls in fullscreen mode. It is not a complete fullscreen implementation, so you will still need to apply a style that makes the width and height match your screen dimensions to get a fullscreen video.
Example:
```
this.player.presentFullscreenPlayer();
```
Platforms: Android ExoPlayer, Android MediaPlayer, iOS
Platforms: Android, iOS
#### save
`save(): Promise`
@ -1539,7 +1478,7 @@ android {
```
#### ExoPlayer no longer detaches
When using a router like the react-navigation TabNavigator, switching between tab routes would previously cause ExoPlayer to detach causing the video player to pause. We now don't detach the view, allowing the video to continue playing in a background tab. This matches the behavior for iOS. Android MediaPlayer will crash if it detaches when switching routes, so its behavior has not been changed.
When using a router like the react-navigation TabNavigator, switching between tab routes would previously cause ExoPlayer to detach causing the video player to pause. We now don't detach the view, allowing the video to continue playing in a background tab. This matches the behavior for iOS.
#### useTextureView now defaults to true
The SurfaceView, which ExoPlayer has been using by default has a number of quirks that people are unaware of and often cause issues. This includes not supporting animations or scaling. It also causes strange behavior if you overlay two videos on top of each other, because the SurfaceView will [punch a hole](https://developer.android.com/reference/android/view/SurfaceView) through other views. Since TextureView doesn't have these issues and behaves in the way most developers expect, it makes sense to make it the default.

View File

@ -1,8 +1,18 @@
## Changelog
### Version 6.0.0-alpha1
### Version 6.0.0-alpha.2
- Fix Exoplayer progress not reported when paused [#2664](https://github.com/react-native-video/react-native-video/pull/2664)
### Version 6.0.0-alpha.1
- Remove Android MediaPlayer support [#2724](https://github.com/react-native-video/react-native-video/pull/2724)
- Replace Image.propTypes with ImagePropTypes. [#2718](https://github.com/react-native-video/react-native-video/pull/2718)
- Fix iOS build caused by type mismatch [#2720](https://github.com/react-native-video/react-native-video/pull/2720)
- ERROR TypeError: undefined is not an object (evaluating '_reactNative.Image.propTypes.resizeMode') [#2714](https://github.com/react-native-video/react-native-video/pull/2714)
- Fix video endless loop when repeat set to false or not specified. [#2329](https://github.com/react-native-video/react-native-video/pull/2329)
### Version 6.0.0-alpha.0
- Support disabling buffering [#2689](https://github.com/react-native-video/react-native-video/pull/2689)
- Fix AudioFocus bug that could cause the player to stop responding to play/pause in some instances. [#2689](https://github.com/react-native-video/react-native-video/pull/2689)
- Fix player crashing when it is being cleared. [#2689](https://github.com/react-native-video/react-native-video/pull/2689)
@ -39,6 +49,7 @@
- Change WindowsTargetPlatformVersion to 10.0 [#2706](https://github.com/react-native-video/react-native-video/pull/2706)
- Fixed Android seeking bug [#2712](https://github.com/react-native-video/react-native-video/pull/2712)
- Fixed `onReadyForDisplay` not being called [#2721](https://github.com/react-native-video/react-native-video/pull/2721)
- Fix type of `_eventDispatcher` on iOS target to match `bridge.eventDispatcher()` [#2720](https://github.com/react-native-video/react-native-video/pull/2720)
### Version 5.2.0

View File

@ -1,5 +1,6 @@
MIT License
Copyright (c) 2016-2022 Project contributors
Copyright (c) 2016 Brent Vatne, Baris Sencan
Permission is hereby granted, free of charge, to any person obtaining a copy

View File

@ -1,7 +1,7 @@
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import { StyleSheet, requireNativeComponent, NativeModules, UIManager, View, Image, Platform, findNodeHandle } from 'react-native';
import { ViewPropTypes } from 'deprecated-react-native-prop-types';
import { ViewPropTypes, ImagePropTypes } from 'deprecated-react-native-prop-types';
import resolveAssetSource from 'react-native/Libraries/Image/resolveAssetSource';
import TextTrackType from './TextTrackType';
import FilterType from './FilterType';
@ -426,7 +426,7 @@ Video.propTypes = {
maxBitRate: PropTypes.number,
resizeMode: PropTypes.string,
poster: PropTypes.string,
posterResizeMode: Image.propTypes.resizeMode,
posterResizeMode: ImagePropTypes.resizeMode,
repeat: PropTypes.bool,
automaticallyWaitsToMinimizeStalling: PropTypes.bool,
allowsExternalPlayback: PropTypes.bool,
@ -473,7 +473,6 @@ Video.propTypes = {
bufferForPlaybackAfterRebufferMs: PropTypes.number,
maxHeapAllocationPercent: PropTypes.number,
}),
stereoPan: PropTypes.number,
rate: PropTypes.number,
pictureInPicture: PropTypes.bool,
playInBackground: PropTypes.bool,

View File

@ -1,50 +0,0 @@
apply plugin: 'com.android.library'
def safeExtGet(prop, fallback) {
rootProject.ext.has(prop) ? rootProject.ext.get(prop) : fallback
}
android {
compileSdkVersion safeExtGet('compileSdkVersion', 31)
buildToolsVersion safeExtGet('buildToolsVersion', '30.0.2')
compileOptions {
targetCompatibility JavaVersion.VERSION_1_8
sourceCompatibility JavaVersion.VERSION_1_8
}
defaultConfig {
minSdkVersion safeExtGet('minSdkVersion', 21)
targetSdkVersion safeExtGet('targetSdkVersion', 28)
versionCode 1
versionName "1.0"
}
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
}
repositories {
// Remove this repository line after google releases to google() or mavenCentral()
maven { url "https://dl.google.com/android/maven2" }
}
dependencies {
implementation "com.facebook.react:react-native:${safeExtGet('reactNativeVersion', '+')}"
implementation('com.google.android.exoplayer:exoplayer:2.17.1') {
exclude group: 'com.android.support'
}
// All support libs must use the same version
implementation "androidx.annotation:annotation:1.1.0"
implementation "androidx.core:core:1.1.0"
implementation "androidx.media:media:1.1.0"
implementation('com.google.android.exoplayer:extension-okhttp:2.17.1') {
exclude group: 'com.squareup.okhttp3', module: 'okhttp'
}
implementation 'com.squareup.okhttp3:okhttp:${OKHTTP_VERSION}'
}

View File

@ -1,3 +0,0 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.brentvatne.react">
</manifest>

View File

@ -1,44 +0,0 @@
package com.brentvatne.react;
import com.brentvatne.exoplayer.DefaultReactExoplayerConfig;
import com.brentvatne.exoplayer.ReactExoplayerConfig;
import com.brentvatne.exoplayer.ReactExoplayerViewManager;
import com.facebook.react.ReactPackage;
import com.facebook.react.bridge.JavaScriptModule;
import com.facebook.react.bridge.NativeModule;
import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.uimanager.ViewManager;
import java.util.Collections;
import java.util.List;
public class ReactVideoPackage implements ReactPackage {
private ReactExoplayerConfig config;
public ReactVideoPackage() {
}
public ReactVideoPackage(ReactExoplayerConfig config) {
this.config = config;
}
@Override
public List<NativeModule> createNativeModules(ReactApplicationContext reactContext) {
return Collections.emptyList();
}
// Deprecated RN 0.47
public List<Class<? extends JavaScriptModule>> createJSModules() {
return Collections.emptyList();
}
@Override
public List<ViewManager> createViewManagers(ReactApplicationContext reactContext) {
if (config == null) {
config = new DefaultReactExoplayerConfig(reactContext);
}
return Collections.singletonList(new ReactExoplayerViewManager(config));
}
}

View File

@ -8,19 +8,43 @@ android {
compileSdkVersion safeExtGet('compileSdkVersion', 31)
buildToolsVersion safeExtGet('buildToolsVersion', '30.0.2')
compileOptions {
targetCompatibility JavaVersion.VERSION_1_8
sourceCompatibility JavaVersion.VERSION_1_8
}
defaultConfig {
minSdkVersion safeExtGet('minSdkVersion', 21)
targetSdkVersion safeExtGet('targetSdkVersion', 28)
versionCode 1
versionName "1.0"
ndk {
abiFilters "armeabi-v7a", "x86"
}
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
}
dependencies {
//noinspection GradleDynamicVersion
implementation "com.facebook.react:react-native:${safeExtGet('reactNativeVersion', '+')}"
implementation 'com.github.thang2162:Android-ScalableVideoView:v1.1.1'
repositories {
// Remove this repository line after google releases to google() or mavenCentral()
maven { url "https://dl.google.com/android/maven2" }
}
dependencies {
implementation "com.facebook.react:react-native:${safeExtGet('reactNativeVersion', '+')}"
implementation('com.google.android.exoplayer:exoplayer:2.17.1') {
exclude group: 'com.android.support'
}
// All support libs must use the same version
implementation "androidx.annotation:annotation:1.1.0"
implementation "androidx.core:core:1.1.0"
implementation "androidx.media:media:1.1.0"
implementation('com.google.android.exoplayer:extension-okhttp:2.17.1') {
exclude group: 'com.squareup.okhttp3', module: 'okhttp'
}
implementation 'com.squareup.okhttp3:okhttp:${OKHTTP_VERSION}'
}

View File

@ -1,287 +0,0 @@
package com.android.vending.expansion.zipfile;
/*
* Copyright (C) 2012 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
//To implement APEZProvider in your application, you'll want to change
//the AUTHORITY to match what you define in the manifest.
import com.android.vending.expansion.zipfile.ZipResourceFile.ZipEntryRO;
import android.content.ContentProvider;
import android.content.ContentProviderOperation;
import android.content.ContentProviderResult;
import android.content.ContentValues;
import android.content.Context;
import android.content.OperationApplicationException;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.content.pm.PackageManager.NameNotFoundException;
import android.content.pm.ProviderInfo;
import android.content.res.AssetFileDescriptor;
import android.database.Cursor;
import android.database.MatrixCursor;
import android.net.Uri;
import android.os.ParcelFileDescriptor;
import android.provider.BaseColumns;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.util.ArrayList;
/**
* This content provider is an optional part of the library.
*
* <p>Most apps don't need to use this class. This defines a
* ContentProvider that marshalls the data from the ZIP files through a
* content provider Uri in order to provide file access for certain Android APIs
* that expect Uri access to media files.
*
*/
public abstract class APEZProvider extends ContentProvider {
private ZipResourceFile mAPKExtensionFile;
private boolean mInit;
public static final String FILEID = BaseColumns._ID;
public static final String FILENAME = "ZPFN";
public static final String ZIPFILE = "ZFIL";
public static final String MODIFICATION = "ZMOD";
public static final String CRC32 = "ZCRC";
public static final String COMPRESSEDLEN = "ZCOL";
public static final String UNCOMPRESSEDLEN = "ZUNL";
public static final String COMPRESSIONTYPE = "ZTYP";
public static final String[] ALL_FIELDS = {
FILEID,
FILENAME,
ZIPFILE,
MODIFICATION,
CRC32,
COMPRESSEDLEN,
UNCOMPRESSEDLEN,
COMPRESSIONTYPE
};
public static final int FILEID_IDX = 0;
public static final int FILENAME_IDX = 1;
public static final int ZIPFILE_IDX = 2;
public static final int MOD_IDX = 3;
public static final int CRC_IDX = 4;
public static final int COMPLEN_IDX = 5;
public static final int UNCOMPLEN_IDX = 6;
public static final int COMPTYPE_IDX = 7;
public static final int[] ALL_FIELDS_INT = {
FILEID_IDX,
FILENAME_IDX,
ZIPFILE_IDX,
MOD_IDX,
CRC_IDX,
COMPLEN_IDX,
UNCOMPLEN_IDX,
COMPTYPE_IDX
};
/**
* This needs to match the authority in your manifest
*/
public abstract String getAuthority();
@Override
public int delete(Uri arg0, String arg1, String[] arg2) {
// TODO Auto-generated method stub
return 0;
}
@Override
public String getType(Uri uri) {
return "vnd.android.cursor.item/asset";
}
@Override
public Uri insert(Uri uri, ContentValues values) {
// TODO Auto-generated method stub
return null;
}
static private final String NO_FILE = "N";
private boolean initIfNecessary() {
if ( !mInit ) {
Context ctx = getContext();
PackageManager pm = ctx.getPackageManager();
ProviderInfo pi = pm.resolveContentProvider(getAuthority(), PackageManager.GET_META_DATA);
PackageInfo packInfo;
try {
packInfo = pm.getPackageInfo(ctx.getPackageName(), 0);
} catch (NameNotFoundException e1) {
e1.printStackTrace();
return false;
}
int patchFileVersion;
int mainFileVersion;
int appVersionCode = packInfo.versionCode;
String[] resourceFiles = null;
if ( null != pi.metaData ) {
mainFileVersion = pi.metaData.getInt("mainVersion", appVersionCode);
patchFileVersion = pi.metaData.getInt("patchVersion", appVersionCode);
String mainFileName = pi.metaData.getString("mainFilename", NO_FILE);
if ( NO_FILE != mainFileName ) {
String patchFileName = pi.metaData.getString("patchFilename", NO_FILE);
if ( NO_FILE != patchFileName ) {
resourceFiles = new String[] { mainFileName, patchFileName };
} else {
resourceFiles = new String[] { mainFileName };
}
}
} else {
mainFileVersion = patchFileVersion = appVersionCode;
}
try {
if ( null == resourceFiles ) {
mAPKExtensionFile = APKExpansionSupport.getAPKExpansionZipFile(ctx, mainFileVersion, patchFileVersion);
} else {
mAPKExtensionFile = APKExpansionSupport.getResourceZipFile(resourceFiles);
}
mInit = true;
return true;
} catch (IOException e) {
e.printStackTrace();
}
}
return false;
}
@Override
public boolean onCreate() {
return true;
}
@Override
public AssetFileDescriptor openAssetFile(Uri uri, String mode)
throws FileNotFoundException {
initIfNecessary();
String path = uri.getEncodedPath();
if ( path.startsWith("/") ) {
path = path.substring(1);
}
return mAPKExtensionFile.getAssetFileDescriptor(path);
}
@Override
public ContentProviderResult[] applyBatch(
ArrayList<ContentProviderOperation> operations)
throws OperationApplicationException {
initIfNecessary();
return super.applyBatch(operations);
}
@Override
public ParcelFileDescriptor openFile(Uri uri, String mode)
throws FileNotFoundException {
initIfNecessary();
AssetFileDescriptor af = openAssetFile(uri, mode);
if ( null != af ) {
return af.getParcelFileDescriptor();
}
return null;
}
@Override
public Cursor query(Uri uri, String[] projection, String selection,
String[] selectionArgs, String sortOrder) {
initIfNecessary();
// lists all of the items in the file that match
ZipEntryRO[] zipEntries;
if ( null == mAPKExtensionFile ) {
zipEntries = new ZipEntryRO[0];
} else {
zipEntries = mAPKExtensionFile.getAllEntries();
}
int[] intProjection;
if ( null == projection ) {
intProjection = ALL_FIELDS_INT;
projection = ALL_FIELDS;
} else {
int len = projection.length;
intProjection = new int[len];
for ( int i = 0; i < len; i++ ) {
if ( projection[i].equals(FILEID) ) {
intProjection[i] = FILEID_IDX;
} else if ( projection[i].equals(FILENAME) ) {
intProjection[i] = FILENAME_IDX;
} else if ( projection[i].equals(ZIPFILE) ) {
intProjection[i] = ZIPFILE_IDX;
} else if ( projection[i].equals(MODIFICATION) ) {
intProjection[i] = MOD_IDX;
} else if ( projection[i].equals(CRC32) ) {
intProjection[i] = CRC_IDX;
} else if ( projection[i].equals(COMPRESSEDLEN) ) {
intProjection[i] = COMPLEN_IDX;
} else if ( projection[i].equals(UNCOMPRESSEDLEN) ) {
intProjection[i] = UNCOMPLEN_IDX;
} else if ( projection[i].equals(COMPRESSIONTYPE) ) {
intProjection[i] = COMPTYPE_IDX;
} else {
throw new RuntimeException();
}
}
}
MatrixCursor mc = new MatrixCursor(projection, zipEntries.length);
int len = intProjection.length;
for ( ZipEntryRO zer : zipEntries ) {
MatrixCursor.RowBuilder rb = mc.newRow();
for ( int i = 0; i < len; i++ ) {
switch (intProjection[i]) {
case FILEID_IDX:
rb.add(i);
break;
case FILENAME_IDX:
rb.add(zer.mFileName);
break;
case ZIPFILE_IDX:
rb.add(zer.getZipFileName());
break;
case MOD_IDX:
rb.add(zer.mWhenModified);
break;
case CRC_IDX:
rb.add(zer.mCRC32);
break;
case COMPLEN_IDX:
rb.add(zer.mCompressedLength);
break;
case UNCOMPLEN_IDX:
rb.add(zer.mUncompressedLength);
break;
case COMPTYPE_IDX:
rb.add(zer.mMethod);
break;
}
}
}
return mc;
}
@Override
public int update(Uri uri, ContentValues values, String selection,
String[] selectionArgs) {
// TODO Auto-generated method stub
return 0;
}
}

View File

@ -1,81 +0,0 @@
package com.android.vending.expansion.zipfile;
/*
* Copyright (C) 2012 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import java.io.File;
import java.io.FileFilter;
import java.io.IOException;
import java.util.Vector;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import android.content.Context;
import android.os.Environment;
import android.util.Log;
public class APKExpansionSupport {
// The shared path to all app expansion files
private final static String EXP_PATH = "/Android/obb/";
static String[] getAPKExpansionFiles(Context ctx, int mainVersion, int patchVersion) {
String packageName = ctx.getPackageName();
Vector<String> ret = new Vector<String>();
if (Environment.getExternalStorageState().equals(
Environment.MEDIA_MOUNTED)) {
// Build the full path to the app's expansion files
File root = Environment.getExternalStorageDirectory();
File expPath = new File(root.toString() + EXP_PATH + packageName);
// Check that expansion file path exists
if (expPath.exists()) {
if ( mainVersion > 0 ) {
String strMainPath = expPath + File.separator + "main." + mainVersion + "." + packageName + ".obb";
// Log.d("APKEXPANSION", strMainPath);
File main = new File(strMainPath);
if ( main.isFile() ) {
ret.add(strMainPath);
}
}
if ( patchVersion > 0 ) {
String strPatchPath = expPath + File.separator + "patch." + patchVersion + "." + packageName + ".obb";
File main = new File(strPatchPath);
if ( main.isFile() ) {
ret.add(strPatchPath);
}
}
}
}
String[] retArray = new String[ret.size()];
ret.toArray(retArray);
return retArray;
}
static public ZipResourceFile getResourceZipFile(String[] expansionFiles) throws IOException {
ZipResourceFile apkExpansionFile = null;
for (String expansionFilePath : expansionFiles) {
if ( null == apkExpansionFile ) {
apkExpansionFile = new ZipResourceFile(expansionFilePath);
} else {
apkExpansionFile.addPatchFile(expansionFilePath);
}
}
return apkExpansionFile;
}
static public ZipResourceFile getAPKExpansionZipFile(Context ctx, int mainVersion, int patchVersion) throws IOException{
String[] expansionFiles = getAPKExpansionFiles(ctx, mainVersion, patchVersion);
return getResourceZipFile(expansionFiles);
}
}

View File

@ -1,428 +0,0 @@
package com.android.vending.expansion.zipfile;
/*
* Copyright (C) 2012 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import android.content.res.AssetFileDescriptor;
import android.os.ParcelFileDescriptor;
import android.util.Log;
import java.io.EOFException;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.io.RandomAccessFile;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.MappedByteBuffer;
import java.nio.channels.FileChannel;
import java.util.Collection;
import java.util.HashMap;
import java.util.Vector;
import java.util.zip.ZipEntry;
import java.util.zip.ZipFile;
public class ZipResourceFile {
//
// Read-only access to Zip archives, with minimal heap allocation.
//
static final String LOG_TAG = "zipro";
static final boolean LOGV = false;
// 4-byte number
static private int swapEndian(int i)
{
return ((i & 0xff) << 24) + ((i & 0xff00) << 8) + ((i & 0xff0000) >>> 8)
+ ((i >>> 24) & 0xff);
}
// 2-byte number
static private int swapEndian(short i)
{
return ((i & 0x00FF) << 8 | (i & 0xFF00) >>> 8);
}
/*
* Zip file constants.
*/
static final int kEOCDSignature = 0x06054b50;
static final int kEOCDLen = 22;
static final int kEOCDNumEntries = 8; // offset to #of entries in file
static final int kEOCDSize = 12; // size of the central directory
static final int kEOCDFileOffset = 16; // offset to central directory
static final int kMaxCommentLen = 65535; // longest possible in ushort
static final int kMaxEOCDSearch = (kMaxCommentLen + kEOCDLen);
static final int kLFHSignature = 0x04034b50;
static final int kLFHLen = 30; // excluding variable-len fields
static final int kLFHNameLen = 26; // offset to filename length
static final int kLFHExtraLen = 28; // offset to extra length
static final int kCDESignature = 0x02014b50;
static final int kCDELen = 46; // excluding variable-len fields
static final int kCDEMethod = 10; // offset to compression method
static final int kCDEModWhen = 12; // offset to modification timestamp
static final int kCDECRC = 16; // offset to entry CRC
static final int kCDECompLen = 20; // offset to compressed length
static final int kCDEUncompLen = 24; // offset to uncompressed length
static final int kCDENameLen = 28; // offset to filename length
static final int kCDEExtraLen = 30; // offset to extra length
static final int kCDECommentLen = 32; // offset to comment length
static final int kCDELocalOffset = 42; // offset to local hdr
static final int kCompressStored = 0; // no compression
static final int kCompressDeflated = 8; // standard deflate
/*
* The values we return for ZipEntryRO use 0 as an invalid value, so we want
* to adjust the hash table index by a fixed amount. Using a large value
* helps insure that people don't mix & match arguments, e.g. to
* findEntryByIndex().
*/
static final int kZipEntryAdj = 10000;
static public final class ZipEntryRO {
public ZipEntryRO(final String zipFileName, final File file, final String fileName) {
mFileName = fileName;
mZipFileName = zipFileName;
mFile = file;
}
public final File mFile;
public final String mFileName;
public final String mZipFileName;
public long mLocalHdrOffset; // offset of local file header
/* useful stuff from the directory entry */
public int mMethod;
public long mWhenModified;
public long mCRC32;
public long mCompressedLength;
public long mUncompressedLength;
public long mOffset = -1;
public void setOffsetFromFile(RandomAccessFile f, ByteBuffer buf) throws IOException {
long localHdrOffset = mLocalHdrOffset;
try {
f.seek(localHdrOffset);
f.readFully(buf.array());
if (buf.getInt(0) != kLFHSignature) {
Log.w(LOG_TAG, "didn't find signature at start of lfh");
throw new IOException();
}
int nameLen = buf.getShort(kLFHNameLen) & 0xFFFF;
int extraLen = buf.getShort(kLFHExtraLen) & 0xFFFF;
mOffset = localHdrOffset + kLFHLen + nameLen + extraLen;
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException ioe) {
ioe.printStackTrace();
}
}
/**
* Calculates the offset of the start of the Zip file entry within the
* Zip file.
*
* @return the offset, in bytes from the start of the file of the entry
*/
public long getOffset() {
return mOffset;
}
/**
* isUncompressed
*
* @return true if the file is stored in uncompressed form
*/
public boolean isUncompressed() {
return mMethod == kCompressStored;
}
public AssetFileDescriptor getAssetFileDescriptor() {
if (mMethod == kCompressStored) {
ParcelFileDescriptor pfd;
try {
pfd = ParcelFileDescriptor.open(mFile, ParcelFileDescriptor.MODE_READ_ONLY);
return new AssetFileDescriptor(pfd, getOffset(), mUncompressedLength);
} catch (FileNotFoundException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
return null;
}
public String getZipFileName() {
return mZipFileName;
}
public File getZipFile() {
return mFile;
}
}
private HashMap<String, ZipEntryRO> mHashMap = new HashMap<String, ZipEntryRO>();
/* for reading compressed files */
public HashMap<File, ZipFile> mZipFiles = new HashMap<File, ZipFile>();
public ZipResourceFile(String zipFileName) throws IOException {
addPatchFile(zipFileName);
}
ZipEntryRO[] getEntriesAt(String path) {
Vector<ZipEntryRO> zev = new Vector<ZipEntryRO>();
Collection<ZipEntryRO> values = mHashMap.values();
if (null == path)
path = "";
int length = path.length();
for (ZipEntryRO ze : values) {
if (ze.mFileName.startsWith(path)) {
if (-1 == ze.mFileName.indexOf('/', length)) {
zev.add(ze);
}
}
}
ZipEntryRO[] entries = new ZipEntryRO[zev.size()];
return zev.toArray(entries);
}
public ZipEntryRO[] getAllEntries() {
Collection<ZipEntryRO> values = mHashMap.values();
return values.toArray(new ZipEntryRO[values.size()]);
}
/**
* getAssetFileDescriptor allows for ZipResourceFile to directly feed
* Android API's that want an fd, offset, and length such as the
* MediaPlayer. It also allows for the class to be used in a content
* provider that can feed video players. The file must be stored
* (non-compressed) in the Zip file for this to work.
*
* @param assetPath
* @return the asset file descriptor for the file, or null if the file isn't
* present or is stored compressed
*/
public AssetFileDescriptor getAssetFileDescriptor(String assetPath) {
ZipEntryRO entry = mHashMap.get(assetPath);
if (null != entry) {
return entry.getAssetFileDescriptor();
}
return null;
}
/**
* getInputStream returns an AssetFileDescriptor.AutoCloseInputStream
* associated with the asset that is contained in the Zip file, or a
* standard ZipInputStream if necessary to uncompress the file
*
* @param assetPath
* @return an input stream for the named asset path, or null if not found
* @throws IOException
*/
public InputStream getInputStream(String assetPath) throws IOException {
ZipEntryRO entry = mHashMap.get(assetPath);
if (null != entry) {
if (entry.isUncompressed()) {
return entry.getAssetFileDescriptor().createInputStream();
} else {
ZipFile zf = mZipFiles.get(entry.getZipFile());
/** read compressed files **/
if (null == zf) {
zf = new ZipFile(entry.getZipFile(), ZipFile.OPEN_READ);
mZipFiles.put(entry.getZipFile(), zf);
}
ZipEntry zi = zf.getEntry(assetPath);
if (null != zi)
return zf.getInputStream(zi);
}
}
return null;
}
ByteBuffer mLEByteBuffer = ByteBuffer.allocate(4);
static private int read4LE(RandomAccessFile f) throws EOFException, IOException {
return swapEndian(f.readInt());
}
/*
* Opens the specified file read-only. We memory-map the entire thing and
* close the file before returning.
*/
void addPatchFile(String zipFileName) throws IOException
{
File file = new File(zipFileName);
RandomAccessFile f = new RandomAccessFile(file, "r");
long fileLength = f.length();
if (fileLength < kEOCDLen) {
throw new java.io.IOException();
}
long readAmount = kMaxEOCDSearch;
if (readAmount > fileLength)
readAmount = fileLength;
/*
* Make sure this is a Zip archive.
*/
f.seek(0);
int header = read4LE(f);
if (header == kEOCDSignature) {
Log.i(LOG_TAG, "Found Zip archive, but it looks empty");
throw new IOException();
} else if (header != kLFHSignature) {
Log.v(LOG_TAG, "Not a Zip archive");
throw new IOException();
}
/*
* Perform the traditional EOCD snipe hunt. We're searching for the End
* of Central Directory magic number, which appears at the start of the
* EOCD block. It's followed by 18 bytes of EOCD stuff and up to 64KB of
* archive comment. We need to read the last part of the file into a
* buffer, dig through it to find the magic number, parse some values
* out, and use those to determine the extent of the CD. We start by
* pulling in the last part of the file.
*/
long searchStart = fileLength - readAmount;
f.seek(searchStart);
ByteBuffer bbuf = ByteBuffer.allocate((int) readAmount);
byte[] buffer = bbuf.array();
f.readFully(buffer);
bbuf.order(ByteOrder.LITTLE_ENDIAN);
/*
* Scan backward for the EOCD magic. In an archive without a trailing
* comment, we'll find it on the first try. (We may want to consider
* doing an initial minimal read; if we don't find it, retry with a
* second read as above.)
*/
// EOCD == 0x50, 0x4b, 0x05, 0x06
int eocdIdx;
for (eocdIdx = buffer.length - kEOCDLen; eocdIdx >= 0; eocdIdx--) {
if (buffer[eocdIdx] == 0x50 && bbuf.getInt(eocdIdx) == kEOCDSignature)
{
if (LOGV) {
Log.v(LOG_TAG, "+++ Found EOCD at index: " + eocdIdx);
}
break;
}
}
if (eocdIdx < 0) {
Log.d(LOG_TAG, "Zip: EOCD not found, " + zipFileName + " is not zip");
}
/*
* Grab the CD offset and size, and the number of entries in the
* archive. After that, we can release our EOCD hunt buffer.
*/
int numEntries = bbuf.getShort(eocdIdx + kEOCDNumEntries);
long dirSize = bbuf.getInt(eocdIdx + kEOCDSize) & 0xffffffffL;
long dirOffset = bbuf.getInt(eocdIdx + kEOCDFileOffset) & 0xffffffffL;
// Verify that they look reasonable.
if (dirOffset + dirSize > fileLength) {
Log.w(LOG_TAG, "bad offsets (dir " + dirOffset + ", size " + dirSize + ", eocd "
+ eocdIdx + ")");
throw new IOException();
}
if (numEntries == 0) {
Log.w(LOG_TAG, "empty archive?");
throw new IOException();
}
if (LOGV) {
Log.v(LOG_TAG, "+++ numEntries=" + numEntries + " dirSize=" + dirSize + " dirOffset="
+ dirOffset);
}
MappedByteBuffer directoryMap = f.getChannel()
.map(FileChannel.MapMode.READ_ONLY, dirOffset, dirSize);
directoryMap.order(ByteOrder.LITTLE_ENDIAN);
byte[] tempBuf = new byte[0xffff];
/*
* Walk through the central directory, adding entries to the hash table.
*/
int currentOffset = 0;
/*
* Allocate the local directory information
*/
ByteBuffer buf = ByteBuffer.allocate(kLFHLen);
buf.order(ByteOrder.LITTLE_ENDIAN);
for (int i = 0; i < numEntries; i++) {
if (directoryMap.getInt(currentOffset) != kCDESignature) {
Log.w(LOG_TAG, "Missed a central dir sig (at " + currentOffset + ")");
throw new IOException();
}
/* useful stuff from the directory entry */
int fileNameLen = directoryMap.getShort(currentOffset + kCDENameLen) & 0xffff;
int extraLen = directoryMap.getShort(currentOffset + kCDEExtraLen) & 0xffff;
int commentLen = directoryMap.getShort(currentOffset + kCDECommentLen) & 0xffff;
/* get the CDE filename */
directoryMap.position(currentOffset + kCDELen);
directoryMap.get(tempBuf, 0, fileNameLen);
directoryMap.position(0);
/* UTF-8 on Android */
String str = new String(tempBuf, 0, fileNameLen);
if (LOGV) {
Log.v(LOG_TAG, "Filename: " + str);
}
ZipEntryRO ze = new ZipEntryRO(zipFileName, file, str);
ze.mMethod = directoryMap.getShort(currentOffset + kCDEMethod) & 0xffff;
ze.mWhenModified = directoryMap.getInt(currentOffset + kCDEModWhen) & 0xffffffffL;
ze.mCRC32 = directoryMap.getLong(currentOffset + kCDECRC) & 0xffffffffL;
ze.mCompressedLength = directoryMap.getLong(currentOffset + kCDECompLen) & 0xffffffffL;
ze.mUncompressedLength = directoryMap.getLong(currentOffset + kCDEUncompLen) & 0xffffffffL;
ze.mLocalHdrOffset = directoryMap.getInt(currentOffset + kCDELocalOffset) & 0xffffffffL;
// set the offsets
buf.clear();
ze.setOffsetFromFile(f, buf);
// put file into hash
mHashMap.put(str, ze);
// go to next directory entry
currentOffset += kCDELen + fileNameLen + extraLen + commentLen;
}
if (LOGV) {
Log.v(LOG_TAG, "+++ zip good scan " + numEntries + " entries");
}
}
}

View File

@ -1,18 +1,28 @@
package com.brentvatne.react;
import android.app.Activity;
import com.brentvatne.exoplayer.DefaultReactExoplayerConfig;
import com.brentvatne.exoplayer.ReactExoplayerConfig;
import com.brentvatne.exoplayer.ReactExoplayerViewManager;
import com.facebook.react.ReactPackage;
import com.facebook.react.bridge.JavaScriptModule;
import com.facebook.react.bridge.NativeModule;
import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.uimanager.ViewManager;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
public class ReactVideoPackage implements ReactPackage {
private ReactExoplayerConfig config;
public ReactVideoPackage() {
}
public ReactVideoPackage(ReactExoplayerConfig config) {
this.config = config;
}
@Override
public List<NativeModule> createNativeModules(ReactApplicationContext reactContext) {
return Collections.emptyList();
@ -23,8 +33,12 @@ public class ReactVideoPackage implements ReactPackage {
return Collections.emptyList();
}
@Override
public List<ViewManager> createViewManagers(ReactApplicationContext reactContext) {
return Arrays.<ViewManager>asList(new ReactVideoViewManager());
if (config == null) {
config = new DefaultReactExoplayerConfig(reactContext);
}
return Collections.singletonList(new ReactExoplayerViewManager(config));
}
}

View File

@ -1,796 +0,0 @@
package com.brentvatne.react;
import android.annotation.SuppressLint;
import android.annotation.TargetApi;
import android.app.Activity;
import android.content.res.AssetFileDescriptor;
import android.graphics.Matrix;
import android.media.MediaPlayer;
import android.media.TimedMetaData;
import android.net.Uri;
import android.os.Build;
import android.os.Handler;
import android.util.Log;
import android.view.MotionEvent;
import android.view.WindowManager;
import android.view.View;
import android.view.Window;
import android.webkit.CookieManager;
import android.widget.MediaController;
import com.android.vending.expansion.zipfile.APKExpansionSupport;
import com.android.vending.expansion.zipfile.ZipResourceFile;
import com.facebook.react.bridge.Arguments;
import com.facebook.react.bridge.LifecycleEventListener;
import com.facebook.react.bridge.ReadableMap;
import com.facebook.react.bridge.WritableMap;
import com.facebook.react.bridge.WritableArray;
import com.facebook.react.bridge.WritableNativeArray;
import com.facebook.react.uimanager.ThemedReactContext;
import com.facebook.react.uimanager.events.RCTEventEmitter;
import com.yqritc.scalablevideoview.ScalableType;
import com.yqritc.scalablevideoview.ScalableVideoView;
import com.yqritc.scalablevideoview.ScaleManager;
import com.yqritc.scalablevideoview.Size;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.util.HashMap;
import java.util.Map;
import java.lang.Math;
import java.math.BigDecimal;
import javax.annotation.Nullable;
@SuppressLint("ViewConstructor")
public class ReactVideoView extends ScalableVideoView implements
MediaPlayer.OnPreparedListener,
MediaPlayer.OnErrorListener,
MediaPlayer.OnBufferingUpdateListener,
MediaPlayer.OnSeekCompleteListener,
MediaPlayer.OnCompletionListener,
MediaPlayer.OnInfoListener,
LifecycleEventListener,
MediaController.MediaPlayerControl {
public enum Events {
EVENT_LOAD_START("onVideoLoadStart"),
EVENT_LOAD("onVideoLoad"),
EVENT_ERROR("onVideoError"),
EVENT_PROGRESS("onVideoProgress"),
EVENT_TIMED_METADATA("onTimedMetadata"),
EVENT_SEEK("onVideoSeek"),
EVENT_END("onVideoEnd"),
EVENT_STALLED("onPlaybackStalled"),
EVENT_RESUME("onPlaybackResume"),
EVENT_READY_FOR_DISPLAY("onReadyForDisplay"),
EVENT_FULLSCREEN_WILL_PRESENT("onVideoFullscreenPlayerWillPresent"),
EVENT_FULLSCREEN_DID_PRESENT("onVideoFullscreenPlayerDidPresent"),
EVENT_FULLSCREEN_WILL_DISMISS("onVideoFullscreenPlayerWillDismiss"),
EVENT_FULLSCREEN_DID_DISMISS("onVideoFullscreenPlayerDidDismiss");
private final String mName;
Events(final String name) {
mName = name;
}
@Override
public String toString() {
return mName;
}
}
public static final String EVENT_PROP_FAST_FORWARD = "canPlayFastForward";
public static final String EVENT_PROP_SLOW_FORWARD = "canPlaySlowForward";
public static final String EVENT_PROP_SLOW_REVERSE = "canPlaySlowReverse";
public static final String EVENT_PROP_REVERSE = "canPlayReverse";
public static final String EVENT_PROP_STEP_FORWARD = "canStepForward";
public static final String EVENT_PROP_STEP_BACKWARD = "canStepBackward";
public static final String EVENT_PROP_DURATION = "duration";
public static final String EVENT_PROP_PLAYABLE_DURATION = "playableDuration";
public static final String EVENT_PROP_SEEKABLE_DURATION = "seekableDuration";
public static final String EVENT_PROP_CURRENT_TIME = "currentTime";
public static final String EVENT_PROP_SEEK_TIME = "seekTime";
public static final String EVENT_PROP_NATURALSIZE = "naturalSize";
public static final String EVENT_PROP_WIDTH = "width";
public static final String EVENT_PROP_HEIGHT = "height";
public static final String EVENT_PROP_ORIENTATION = "orientation";
public static final String EVENT_PROP_METADATA = "metadata";
public static final String EVENT_PROP_TARGET = "target";
public static final String EVENT_PROP_METADATA_IDENTIFIER = "identifier";
public static final String EVENT_PROP_METADATA_VALUE = "value";
public static final String EVENT_PROP_ERROR = "error";
public static final String EVENT_PROP_WHAT = "what";
public static final String EVENT_PROP_EXTRA = "extra";
private ThemedReactContext mThemedReactContext;
private RCTEventEmitter mEventEmitter;
private Handler mProgressUpdateHandler = new Handler();
private Runnable mProgressUpdateRunnable = null;
private Handler videoControlHandler = new Handler();
private MediaController mediaController;
private String mSrcUriString = null;
private String mSrcType = "mp4";
private ReadableMap mRequestHeaders = null;
private boolean mSrcIsNetwork = false;
private boolean mSrcIsAsset = false;
private ScalableType mResizeMode = ScalableType.LEFT_TOP;
private boolean mRepeat = false;
private boolean mPaused = false;
private boolean mMuted = false;
private boolean mPreventsDisplaySleepDuringVideoPlayback = true;
private float mVolume = 1.0f;
private float mStereoPan = 0.0f;
private float mProgressUpdateInterval = 250.0f;
private float mRate = 1.0f;
private float mActiveRate = 1.0f;
private long mSeekTime = 0;
private boolean mPlayInBackground = false;
private boolean mBackgroundPaused = false;
private boolean mIsFullscreen = false;
private int mMainVer = 0;
private int mPatchVer = 0;
private boolean mMediaPlayerValid = false; // True if mMediaPlayer is in prepared, started, paused or completed state.
private int mVideoDuration = 0;
private int mVideoBufferedDuration = 0;
private boolean isCompleted = false;
private boolean mUseNativeControls = false;
public ReactVideoView(ThemedReactContext themedReactContext) {
super(themedReactContext);
mThemedReactContext = themedReactContext;
mEventEmitter = themedReactContext.getJSModule(RCTEventEmitter.class);
themedReactContext.addLifecycleEventListener(this);
initializeMediaPlayerIfNeeded();
setSurfaceTextureListener(this);
mProgressUpdateRunnable = new Runnable() {
@Override
public void run() {
if (mMediaPlayerValid && !isCompleted && !mPaused && !mBackgroundPaused) {
WritableMap event = Arguments.createMap();
event.putDouble(EVENT_PROP_CURRENT_TIME, mMediaPlayer.getCurrentPosition() / 1000.0);
event.putDouble(EVENT_PROP_PLAYABLE_DURATION, mVideoBufferedDuration / 1000.0); //TODO:mBufferUpdateRunnable
event.putDouble(EVENT_PROP_SEEKABLE_DURATION, mVideoDuration / 1000.0);
mEventEmitter.receiveEvent(getId(), Events.EVENT_PROGRESS.toString(), event);
// Check for update after an interval
mProgressUpdateHandler.postDelayed(mProgressUpdateRunnable, Math.round(mProgressUpdateInterval));
}
}
};
}
@Override
public boolean onTouchEvent(MotionEvent event) {
if (mUseNativeControls) {
initializeMediaControllerIfNeeded();
mediaController.show();
}
return super.onTouchEvent(event);
}
@Override
@SuppressLint("DrawAllocation")
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
super.onLayout(changed, left, top, right, bottom);
if (!changed || !mMediaPlayerValid) {
return;
}
int videoWidth = getVideoWidth();
int videoHeight = getVideoHeight();
if (videoWidth == 0 || videoHeight == 0) {
return;
}
Size viewSize = new Size(getWidth(), getHeight());
Size videoSize = new Size(videoWidth, videoHeight);
ScaleManager scaleManager = new ScaleManager(viewSize, videoSize);
Matrix matrix = scaleManager.getScaleMatrix(mScalableType);
if (matrix != null) {
setTransform(matrix);
}
}
private void initializeMediaPlayerIfNeeded() {
if (mMediaPlayer == null) {
mMediaPlayerValid = false;
mMediaPlayer = new MediaPlayer();
mMediaPlayer.setOnVideoSizeChangedListener(this);
mMediaPlayer.setOnErrorListener(this);
mMediaPlayer.setOnPreparedListener(this);
mMediaPlayer.setOnBufferingUpdateListener(this);
mMediaPlayer.setOnSeekCompleteListener(this);
mMediaPlayer.setOnCompletionListener(this);
mMediaPlayer.setOnInfoListener(this);
if (Build.VERSION.SDK_INT >= 23) {
mMediaPlayer.setOnTimedMetaDataAvailableListener(new TimedMetaDataAvailableListener());
}
}
}
private void initializeMediaControllerIfNeeded() {
if (mediaController == null) {
mediaController = new MediaController(this.getContext());
}
}
public void cleanupMediaPlayerResources() {
if ( mediaController != null ) {
mediaController.hide();
}
if ( mMediaPlayer != null ) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
mMediaPlayer.setOnTimedMetaDataAvailableListener(null);
}
mMediaPlayerValid = false;
release();
}
if (mIsFullscreen) {
setFullscreen(false);
}
if (mThemedReactContext != null) {
mThemedReactContext.removeLifecycleEventListener(this);
mThemedReactContext = null;
}
}
public void setSrc(final String uriString, final String type, final boolean isNetwork, final boolean isAsset, final ReadableMap requestHeaders) {
setSrc(uriString, type, isNetwork, isAsset, requestHeaders, 0, 0);
}
public void setSrc(final String uriString, final String type, final boolean isNetwork, final boolean isAsset, final ReadableMap requestHeaders, final int expansionMainVersion, final int expansionPatchVersion) {
mSrcUriString = uriString;
mSrcType = type;
mSrcIsNetwork = isNetwork;
mSrcIsAsset = isAsset;
mRequestHeaders = requestHeaders;
mMainVer = expansionMainVersion;
mPatchVer = expansionPatchVersion;
mMediaPlayerValid = false;
mVideoDuration = 0;
mVideoBufferedDuration = 0;
initializeMediaPlayerIfNeeded();
mMediaPlayer.reset();
try {
if (isNetwork) {
// Use the shared CookieManager to access the cookies
// set by WebViews inside the same app
CookieManager cookieManager = CookieManager.getInstance();
Uri parsedUrl = Uri.parse(uriString);
Uri.Builder builtUrl = parsedUrl.buildUpon();
String cookie = cookieManager.getCookie(builtUrl.build().toString());
Map<String, String> headers = new HashMap<String, String>();
if (cookie != null) {
headers.put("Cookie", cookie);
}
if (mRequestHeaders != null) {
headers.putAll(toStringMap(mRequestHeaders));
}
/* According to https://github.com/react-native-community/react-native-video/pull/537
* there is an issue with this where it can cause a IOException.
* TODO: diagnose this exception and fix it
*/
setDataSource(mThemedReactContext, parsedUrl, headers);
} else if (isAsset) {
if (uriString.startsWith("content://")) {
Uri parsedUrl = Uri.parse(uriString);
setDataSource(mThemedReactContext, parsedUrl);
} else {
setDataSource(uriString);
}
} else {
ZipResourceFile expansionFile= null;
AssetFileDescriptor fd= null;
if(mMainVer>0) {
try {
expansionFile = APKExpansionSupport.getAPKExpansionZipFile(mThemedReactContext, mMainVer, mPatchVer);
fd = expansionFile.getAssetFileDescriptor(uriString.replace(".mp4","") + ".mp4");
} catch (IOException e) {
e.printStackTrace();
} catch (NullPointerException e) {
e.printStackTrace();
}
}
if(fd==null) {
int identifier = mThemedReactContext.getResources().getIdentifier(
uriString,
"drawable",
mThemedReactContext.getPackageName()
);
if (identifier == 0) {
identifier = mThemedReactContext.getResources().getIdentifier(
uriString,
"raw",
mThemedReactContext.getPackageName()
);
}
setRawData(identifier);
}
else {
setDataSource(fd.getFileDescriptor(), fd.getStartOffset(),fd.getLength());
}
}
} catch (Exception e) {
e.printStackTrace();
return;
}
WritableMap src = Arguments.createMap();
WritableMap wRequestHeaders = Arguments.createMap();
wRequestHeaders.merge(mRequestHeaders);
src.putString(ReactVideoViewManager.PROP_SRC_URI, uriString);
src.putString(ReactVideoViewManager.PROP_SRC_TYPE, type);
src.putMap(ReactVideoViewManager.PROP_SRC_HEADERS, wRequestHeaders);
src.putBoolean(ReactVideoViewManager.PROP_SRC_IS_NETWORK, isNetwork);
if(mMainVer>0) {
src.putInt(ReactVideoViewManager.PROP_SRC_MAINVER, mMainVer);
if(mPatchVer>0) {
src.putInt(ReactVideoViewManager.PROP_SRC_PATCHVER, mPatchVer);
}
}
WritableMap event = Arguments.createMap();
event.putMap(ReactVideoViewManager.PROP_SRC, src);
mEventEmitter.receiveEvent(getId(), Events.EVENT_LOAD_START.toString(), event);
isCompleted = false;
try {
prepareAsync(this);
} catch (Exception e) {
e.printStackTrace();
}
}
public void setResizeModeModifier(final ScalableType resizeMode) {
mResizeMode = resizeMode;
if (mMediaPlayerValid) {
setScalableType(resizeMode);
invalidate();
}
}
public void setRepeatModifier(final boolean repeat) {
mRepeat = repeat;
if (mMediaPlayerValid) {
setLooping(repeat);
}
}
public void setPausedModifier(final boolean paused) {
mPaused = paused;
if (!mMediaPlayerValid) {
return;
}
if (mPaused) {
if (mMediaPlayer.isPlaying()) {
pause();
}
} else {
if (!mMediaPlayer.isPlaying()) {
start();
// Setting the rate unpauses, so we have to wait for an unpause
if (mRate != mActiveRate) {
setRateModifier(mRate);
}
// Also Start the Progress Update Handler
mProgressUpdateHandler.post(mProgressUpdateRunnable);
}
}
setKeepScreenOn(!mPaused && mPreventsDisplaySleepDuringVideoPlayback);
}
// reduces the volume based on stereoPan
private float calulateRelativeVolume() {
float relativeVolume = (mVolume * (1 - Math.abs(mStereoPan)));
// only one decimal allowed
BigDecimal roundRelativeVolume = new BigDecimal(relativeVolume).setScale(1, BigDecimal.ROUND_HALF_UP);
return roundRelativeVolume.floatValue();
}
public void setPreventsDisplaySleepDuringVideoPlaybackModifier(final boolean preventsDisplaySleepDuringVideoPlayback) {
mPreventsDisplaySleepDuringVideoPlayback = preventsDisplaySleepDuringVideoPlayback;
if (!mMediaPlayerValid) {
return;
}
mMediaPlayer.setScreenOnWhilePlaying(mPreventsDisplaySleepDuringVideoPlayback);
setKeepScreenOn(mPreventsDisplaySleepDuringVideoPlayback);
}
public void setMutedModifier(final boolean muted) {
mMuted = muted;
if (!mMediaPlayerValid) {
return;
}
if (mMuted) {
setVolume(0, 0);
} else if (mStereoPan < 0) {
// louder on the left channel
setVolume(mVolume, calulateRelativeVolume());
} else if (mStereoPan > 0) {
// louder on the right channel
setVolume(calulateRelativeVolume(), mVolume);
} else {
// same volume on both channels
setVolume(mVolume, mVolume);
}
}
public void setVolumeModifier(final float volume) {
mVolume = volume;
setMutedModifier(mMuted);
}
public void setStereoPan(final float stereoPan) {
mStereoPan = stereoPan;
setMutedModifier(mMuted);
}
public void setProgressUpdateInterval(final float progressUpdateInterval) {
mProgressUpdateInterval = progressUpdateInterval;
}
public void setRateModifier(final float rate) {
mRate = rate;
if (mMediaPlayerValid) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
if (!mPaused) { // Applying the rate while paused will cause the video to start
/* Per https://stackoverflow.com/questions/39442522/setplaybackparams-causes-illegalstateexception
* Some devices throw an IllegalStateException if you set the rate without first calling reset()
* TODO: Call reset() then reinitialize the player
*/
try {
mMediaPlayer.setPlaybackParams(mMediaPlayer.getPlaybackParams().setSpeed(rate));
mActiveRate = rate;
} catch (Exception e) {
Log.e(ReactVideoViewManager.REACT_CLASS, "Unable to set rate, unsupported on this device");
}
}
} else {
Log.e(ReactVideoViewManager.REACT_CLASS, "Setting playback rate is not yet supported on Android versions below 6.0");
}
}
}
public void setFullscreen(boolean isFullscreen) {
if (isFullscreen == mIsFullscreen) {
return; // Avoid generating events when nothing is changing
}
mIsFullscreen = isFullscreen;
Activity activity = mThemedReactContext.getCurrentActivity();
if (activity == null) {
return;
}
Window window = activity.getWindow();
View decorView = window.getDecorView();
int uiOptions;
if (mIsFullscreen) {
if (Build.VERSION.SDK_INT >= 19) { // 4.4+
uiOptions = SYSTEM_UI_FLAG_HIDE_NAVIGATION
| SYSTEM_UI_FLAG_IMMERSIVE_STICKY
| SYSTEM_UI_FLAG_FULLSCREEN;
} else {
uiOptions = SYSTEM_UI_FLAG_HIDE_NAVIGATION
| SYSTEM_UI_FLAG_FULLSCREEN;
}
mEventEmitter.receiveEvent(getId(), Events.EVENT_FULLSCREEN_WILL_PRESENT.toString(), null);
decorView.setSystemUiVisibility(uiOptions);
mEventEmitter.receiveEvent(getId(), Events.EVENT_FULLSCREEN_DID_PRESENT.toString(), null);
} else {
uiOptions = View.SYSTEM_UI_FLAG_VISIBLE;
mEventEmitter.receiveEvent(getId(), Events.EVENT_FULLSCREEN_WILL_DISMISS.toString(), null);
decorView.setSystemUiVisibility(uiOptions);
mEventEmitter.receiveEvent(getId(), Events.EVENT_FULLSCREEN_DID_DISMISS.toString(), null);
}
}
public void applyModifiers() {
setResizeModeModifier(mResizeMode);
setRepeatModifier(mRepeat);
setPausedModifier(mPaused);
setMutedModifier(mMuted);
setPreventsDisplaySleepDuringVideoPlaybackModifier(mPreventsDisplaySleepDuringVideoPlayback);
setProgressUpdateInterval(mProgressUpdateInterval);
setRateModifier(mRate);
}
public void setPlayInBackground(final boolean playInBackground) {
mPlayInBackground = playInBackground;
}
public void setControls(boolean controls) {
this.mUseNativeControls = controls;
}
@Override
public void onPrepared(MediaPlayer mp) {
mMediaPlayerValid = true;
mVideoDuration = mp.getDuration();
WritableMap naturalSize = Arguments.createMap();
naturalSize.putInt(EVENT_PROP_WIDTH, mp.getVideoWidth());
naturalSize.putInt(EVENT_PROP_HEIGHT, mp.getVideoHeight());
if (mp.getVideoWidth() > mp.getVideoHeight())
naturalSize.putString(EVENT_PROP_ORIENTATION, "landscape");
else
naturalSize.putString(EVENT_PROP_ORIENTATION, "portrait");
WritableMap event = Arguments.createMap();
event.putDouble(EVENT_PROP_DURATION, mVideoDuration / 1000.0);
event.putDouble(EVENT_PROP_CURRENT_TIME, mp.getCurrentPosition() / 1000.0);
event.putMap(EVENT_PROP_NATURALSIZE, naturalSize);
// TODO: Actually check if you can.
event.putBoolean(EVENT_PROP_FAST_FORWARD, true);
event.putBoolean(EVENT_PROP_SLOW_FORWARD, true);
event.putBoolean(EVENT_PROP_SLOW_REVERSE, true);
event.putBoolean(EVENT_PROP_REVERSE, true);
event.putBoolean(EVENT_PROP_FAST_FORWARD, true);
event.putBoolean(EVENT_PROP_STEP_BACKWARD, true);
event.putBoolean(EVENT_PROP_STEP_FORWARD, true);
mEventEmitter.receiveEvent(getId(), Events.EVENT_LOAD.toString(), event);
applyModifiers();
if (mUseNativeControls) {
initializeMediaControllerIfNeeded();
mediaController.setMediaPlayer(this);
mediaController.setAnchorView(this);
videoControlHandler.post(new Runnable() {
@Override
public void run() {
mediaController.setEnabled(true);
mediaController.show();
}
});
}
selectTimedMetadataTrack(mp);
}
@Override
public boolean onError(MediaPlayer mp, int what, int extra) {
WritableMap error = Arguments.createMap();
error.putInt(EVENT_PROP_WHAT, what);
error.putInt(EVENT_PROP_EXTRA, extra);
WritableMap event = Arguments.createMap();
event.putMap(EVENT_PROP_ERROR, error);
mEventEmitter.receiveEvent(getId(), Events.EVENT_ERROR.toString(), event);
return true;
}
@Override
public boolean onInfo(MediaPlayer mp, int what, int extra) {
switch (what) {
case MediaPlayer.MEDIA_INFO_BUFFERING_START:
mEventEmitter.receiveEvent(getId(), Events.EVENT_STALLED.toString(), Arguments.createMap());
break;
case MediaPlayer.MEDIA_INFO_BUFFERING_END:
mEventEmitter.receiveEvent(getId(), Events.EVENT_RESUME.toString(), Arguments.createMap());
break;
case MediaPlayer.MEDIA_INFO_VIDEO_RENDERING_START:
mEventEmitter.receiveEvent(getId(), Events.EVENT_READY_FOR_DISPLAY.toString(), Arguments.createMap());
break;
default:
}
return false;
}
@Override
public void onBufferingUpdate(MediaPlayer mp, int percent) {
selectTimedMetadataTrack(mp);
mVideoBufferedDuration = (int) Math.round((double) (mVideoDuration * percent) / 100.0);
}
public void onSeekComplete(MediaPlayer mp) {
WritableMap event = Arguments.createMap();
event.putDouble(EVENT_PROP_CURRENT_TIME, getCurrentPosition() / 1000.0);
event.putDouble(EVENT_PROP_SEEK_TIME, mSeekTime / 1000.0);
mEventEmitter.receiveEvent(getId(), Events.EVENT_SEEK.toString(), event);
mSeekTime = 0;
}
@Override
public void seekTo(int msec) {
if (mMediaPlayerValid) {
mSeekTime = msec;
mMediaPlayer.seekTo(msec, MediaPlayer.SEEK_CLOSEST);
if (isCompleted && mVideoDuration != 0 && msec < mVideoDuration) {
isCompleted = false;
}
}
}
@Override
public int getBufferPercentage() {
return 0;
}
@Override
public boolean canPause() {
return true;
}
@Override
public boolean canSeekBackward() {
return true;
}
@Override
public boolean canSeekForward() {
return true;
}
@Override
public int getAudioSessionId() {
return 0;
}
@Override
public void onCompletion(MediaPlayer mp) {
isCompleted = true;
mEventEmitter.receiveEvent(getId(), Events.EVENT_END.toString(), null);
if (!mRepeat) {
setKeepScreenOn(false);
}
}
// This is not fully tested and does not work for all forms of timed metadata
@TargetApi(23) // 6.0
public class TimedMetaDataAvailableListener
implements MediaPlayer.OnTimedMetaDataAvailableListener
{
public void onTimedMetaDataAvailable(MediaPlayer mp, TimedMetaData data) {
WritableMap event = Arguments.createMap();
try {
String rawMeta = new String(data.getMetaData(), "UTF-8");
WritableMap id3 = Arguments.createMap();
id3.putString(EVENT_PROP_METADATA_VALUE, rawMeta.substring(rawMeta.lastIndexOf("\u0003") + 1));
id3.putString(EVENT_PROP_METADATA_IDENTIFIER, "id3/TDEN");
WritableArray metadata = new WritableNativeArray();
metadata.pushMap(id3);
event.putArray(EVENT_PROP_METADATA, metadata);
event.putDouble(EVENT_PROP_TARGET, getId());
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
}
mEventEmitter.receiveEvent(getId(), Events.EVENT_TIMED_METADATA.toString(), event);
}
}
@Override
protected void onDetachedFromWindow() {
mMediaPlayerValid = false;
super.onDetachedFromWindow();
setKeepScreenOn(false);
}
@Override
protected void onAttachedToWindow() {
super.onAttachedToWindow();
if(mMainVer>0) {
setSrc(mSrcUriString, mSrcType, mSrcIsNetwork, mSrcIsAsset, mRequestHeaders, mMainVer, mPatchVer);
}
else {
setSrc(mSrcUriString, mSrcType, mSrcIsNetwork, mSrcIsAsset, mRequestHeaders);
}
setKeepScreenOn(mPreventsDisplaySleepDuringVideoPlayback);
}
@Override
public void onHostPause() {
if (mMediaPlayerValid && !mPaused && !mPlayInBackground) {
/* Pause the video in background
* Don't update the paused prop, developers should be able to update it on background
* so that when you return to the app the video is paused
*/
mBackgroundPaused = true;
mMediaPlayer.pause();
}
}
@Override
public void onHostResume() {
mBackgroundPaused = false;
if (mMediaPlayerValid && !mPlayInBackground && !mPaused) {
new Handler().post(new Runnable() {
@Override
public void run() {
// Restore original state
setPausedModifier(false);
}
});
}
}
@Override
public void onHostDestroy() {
}
/**
* toStringMap converts a {@link ReadableMap} into a HashMap.
*
* @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'
*/
public static Map<String, String> toStringMap(@Nullable ReadableMap readableMap) {
Map<String, String> result = new HashMap<>();
if (readableMap == null)
return result;
com.facebook.react.bridge.ReadableMapKeySetIterator iterator = readableMap.keySetIterator();
while (iterator.hasNextKey()) {
String key = iterator.nextKey();
result.put(key, readableMap.getString(key));
}
return result;
}
// Select track (so we can use it to listen to timed meta data updates)
private void selectTimedMetadataTrack(MediaPlayer mp) {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) {
return;
}
try { // It's possible this could throw an exception if the framework doesn't support getting track info
MediaPlayer.TrackInfo[] trackInfo = mp.getTrackInfo();
for (int i = 0; i < trackInfo.length; ++i) {
if (trackInfo[i].getTrackType() == MediaPlayer.TrackInfo.MEDIA_TRACK_TYPE_TIMEDTEXT) {
mp.selectTrack(i);
break;
}
}
} catch (Exception e) {}
}
}

View File

@ -1,172 +0,0 @@
package com.brentvatne.react;
import com.brentvatne.react.ReactVideoView.Events;
import com.facebook.react.bridge.Arguments;
import com.facebook.react.bridge.ReadableMap;
import com.facebook.react.bridge.WritableMap;
import com.facebook.react.common.MapBuilder;
import com.facebook.react.uimanager.annotations.ReactProp;
import com.facebook.react.uimanager.SimpleViewManager;
import com.facebook.react.uimanager.ThemedReactContext;
import com.facebook.react.uimanager.events.RCTEventEmitter;
import com.yqritc.scalablevideoview.ScalableType;
import javax.annotation.Nullable;
import java.util.Map;
public class ReactVideoViewManager extends SimpleViewManager<ReactVideoView> {
public static final String REACT_CLASS = "RCTVideo";
public static final String PROP_SRC = "src";
public static final String PROP_SRC_URI = "uri";
public static final String PROP_SRC_TYPE = "type";
public static final String PROP_SRC_HEADERS = "requestHeaders";
public static final String PROP_SRC_IS_NETWORK = "isNetwork";
public static final String PROP_SRC_MAINVER = "mainVer";
public static final String PROP_SRC_PATCHVER = "patchVer";
public static final String PROP_SRC_IS_ASSET = "isAsset";
public static final String PROP_RESIZE_MODE = "resizeMode";
public static final String PROP_REPEAT = "repeat";
public static final String PROP_PAUSED = "paused";
public static final String PROP_MUTED = "muted";
public static final String PROP_PREVENTS_DISPLAY_SLEEP_DURING_VIDEO_PLAYBACK = "preventsDisplaySleepDuringVideoPlayback";
public static final String PROP_VOLUME = "volume";
public static final String PROP_STEREO_PAN = "stereoPan";
public static final String PROP_PROGRESS_UPDATE_INTERVAL = "progressUpdateInterval";
public static final String PROP_SEEK = "seek";
public static final String PROP_RATE = "rate";
public static final String PROP_FULLSCREEN = "fullscreen";
public static final String PROP_PLAY_IN_BACKGROUND = "playInBackground";
public static final String PROP_CONTROLS = "controls";
@Override
public String getName() {
return REACT_CLASS;
}
@Override
protected ReactVideoView createViewInstance(ThemedReactContext themedReactContext) {
return new ReactVideoView(themedReactContext);
}
@Override
public void onDropViewInstance(ReactVideoView view) {
super.onDropViewInstance(view);
view.cleanupMediaPlayerResources();
}
@Override
@Nullable
public Map getExportedCustomDirectEventTypeConstants() {
MapBuilder.Builder builder = MapBuilder.builder();
for (Events event : Events.values()) {
builder.put(event.toString(), MapBuilder.of("registrationName", event.toString()));
}
return builder.build();
}
@Override
@Nullable
public Map getExportedViewConstants() {
return MapBuilder.of(
"ScaleNone", Integer.toString(ScalableType.LEFT_TOP.ordinal()),
"ScaleToFill", Integer.toString(ScalableType.FIT_XY.ordinal()),
"ScaleAspectFit", Integer.toString(ScalableType.FIT_CENTER.ordinal()),
"ScaleAspectFill", Integer.toString(ScalableType.CENTER_CROP.ordinal())
);
}
@ReactProp(name = PROP_SRC)
public void setSrc(final ReactVideoView videoView, @Nullable ReadableMap src) {
int mainVer = src.getInt(PROP_SRC_MAINVER);
int patchVer = src.getInt(PROP_SRC_PATCHVER);
if(mainVer<0) { mainVer = 0; }
if(patchVer<0) { patchVer = 0; }
if(mainVer>0) {
videoView.setSrc(
src.getString(PROP_SRC_URI),
src.getString(PROP_SRC_TYPE),
src.getBoolean(PROP_SRC_IS_NETWORK),
src.getBoolean(PROP_SRC_IS_ASSET),
src.getMap(PROP_SRC_HEADERS),
mainVer,
patchVer
);
}
else {
videoView.setSrc(
src.getString(PROP_SRC_URI),
src.getString(PROP_SRC_TYPE),
src.getBoolean(PROP_SRC_IS_NETWORK),
src.getBoolean(PROP_SRC_IS_ASSET),
src.getMap(PROP_SRC_HEADERS)
);
}
}
@ReactProp(name = PROP_PREVENTS_DISPLAY_SLEEP_DURING_VIDEO_PLAYBACK)
public void setPropPreventsDisplaySleepDuringVideoPlayback(final ReactVideoView videoView, final boolean doPreventSleep) {
videoView.setPreventsDisplaySleepDuringVideoPlaybackModifier(doPreventSleep);
}
@ReactProp(name = PROP_RESIZE_MODE)
public void setResizeMode(final ReactVideoView videoView, final String resizeModeOrdinalString) {
videoView.setResizeModeModifier(ScalableType.values()[Integer.parseInt(resizeModeOrdinalString)]);
}
@ReactProp(name = PROP_REPEAT, defaultBoolean = false)
public void setRepeat(final ReactVideoView videoView, final boolean repeat) {
videoView.setRepeatModifier(repeat);
}
@ReactProp(name = PROP_PAUSED, defaultBoolean = false)
public void setPaused(final ReactVideoView videoView, final boolean paused) {
videoView.setPausedModifier(paused);
}
@ReactProp(name = PROP_MUTED, defaultBoolean = false)
public void setMuted(final ReactVideoView videoView, final boolean muted) {
videoView.setMutedModifier(muted);
}
@ReactProp(name = PROP_VOLUME, defaultFloat = 1.0f)
public void setVolume(final ReactVideoView videoView, final float volume) {
videoView.setVolumeModifier(volume);
}
@ReactProp(name = PROP_STEREO_PAN)
public void setStereoPan(final ReactVideoView videoView, final float stereoPan) {
videoView.setStereoPan(stereoPan);
}
@ReactProp(name = PROP_PROGRESS_UPDATE_INTERVAL, defaultFloat = 250.0f)
public void setProgressUpdateInterval(final ReactVideoView videoView, final float progressUpdateInterval) {
videoView.setProgressUpdateInterval(progressUpdateInterval);
}
@ReactProp(name = PROP_SEEK)
public void setSeek(final ReactVideoView videoView, final float seek) {
videoView.seekTo(Math.round(seek * 1000.0f));
}
@ReactProp(name = PROP_RATE)
public void setRate(final ReactVideoView videoView, final float rate) {
videoView.setRateModifier(rate);
}
@ReactProp(name = PROP_FULLSCREEN, defaultBoolean = false)
public void setFullscreen(final ReactVideoView videoView, final boolean fullscreen) {
videoView.setFullscreen(fullscreen);
}
@ReactProp(name = PROP_PLAY_IN_BACKGROUND, defaultBoolean = false)
public void setPlayInBackground(final ReactVideoView videoView, final boolean playInBackground) {
videoView.setPlayInBackground(playInBackground);
}
@ReactProp(name = PROP_CONTROLS, defaultBoolean = false)
public void setControls(final ReactVideoView videoView, final boolean controls) {
videoView.setControls(controls);
}
}

View File

@ -1,280 +0,0 @@
// @flow
/* eslint-env browser */
import { RCTView, type RCTBridge } from 'react-native-dom';
import shaka from 'shaka-player';
import resizeModes from './resizeModes';
import type { VideoSource } from './types';
import RCTVideoEvent from './RCTVideoEvent';
class RCTVideo extends RCTView {
playPromise: Promise<void> = Promise.resolve();
progressTimer: number;
videoElement: HTMLVideoElement;
onEnd: boolean = false;
onLoad: boolean = false;
onLoadStart: boolean = false;
onProgress: boolean = false;
_paused: boolean = false;
_progressUpdateInterval: number = 250.0;
_savedVolume: number = 1.0;
constructor(bridge: RCTBridge) {
super(bridge);
this.eventDispatcher = bridge.getModuleByName('EventDispatcher');
shaka.polyfill.installAll();
this.onEnd = this.onEnd.bind(this);
this.onLoad = this.onLoad.bind(this);
this.onLoadStart = this.onLoadStart.bind(this);
this.onPlay = this.onPlay.bind(this);
this.onProgress = this.onProgress.bind(this);
this.videoElement = this.initializeVideoElement();
this.videoElement.addEventListener('ended', this.onEnd);
this.videoElement.addEventListener('loadeddata', this.onLoad);
this.videoElement.addEventListener('canplay', this.onReadyForDisplay);
this.videoElement.addEventListener('loadstart', this.onLoadStart);
this.videoElement.addEventListener('pause', this.onPause);
this.videoElement.addEventListener('play', this.onPlay);
this.player = new shaka.Player(this.videoElement);
this.muted = false;
this.rate = 1.0;
this.volume = 1.0;
this.childContainer.appendChild(this.videoElement);
}
detachFromView(view: UIView) {
this.videoElement.removeEventListener('ended', this.onEnd);
this.videoElement.removeEventListener('loadeddata', this.onLoad);
this.videoElement.removeEventListener('canplay', this.onReadyForDisplay);
this.videoElement.removeEventListener('loadstart', this.onLoadStart);
this.videoElement.removeEventListener('pause', this.onPause);
this.videoElement.removeEventListener('play', this.onPlay);
this.stopProgressTimer();
}
initializeVideoElement() {
const elem = document.createElement('video');
Object.assign(elem.style, {
display: 'block',
position: 'absolute',
top: '0',
left: '0',
width: '100%',
height: '100%',
});
return elem;
}
presentFullscreenPlayer() {
this.videoElement.webkitRequestFullScreen();
}
set controls(value: boolean) {
this.videoElement.controls = value;
this.videoElement.style.pointerEvents = value ? 'auto' : '';
}
set id(value: string) {
this.videoElement.id = value;
}
set muted(value: boolean) {
this.videoElement.muted = true;
}
set paused(value: boolean) {
if (value) {
this.videoElement.pause();
} else {
this.requestPlay();
}
this._paused = value;
}
set progressUpdateInterval(value: number) {
this._progressUpdateInterval = value;
this.stopProgressTimer();
if (!this._paused) {
this.startProgressTimer();
}
}
set rate(value: number) {
this.videoElement.defaultPlaybackRate = value; // playbackRate doesn't work on Chrome
this.videoElement.playbackRate = value;
}
set repeat(value: boolean) {
this.videoElement.loop = value;
}
set resizeMode(value: number) {
switch (value) {
case resizeModes.ScaleNone: {
this.videoElement.style.objectFit = 'none';
break;
}
case resizeModes.ScaleToFill: {
this.videoElement.style.objectFit = 'fill';
break;
}
case resizeModes.ScaleAspectFit: {
this.videoElement.style.objectFit = 'contain';
break;
}
case resizeModes.ScaleAspectFill: {
this.videoElement.style.objectFit = 'cover';
break;
}
}
}
set seek(value: number) {
this.videoElement.currentTime = value;
}
set source(value: VideoSource) {
let uri = value.uri;
if (uri.startsWith('blob:')) {
let blob = this.bridge.blobManager.resolveURL(uri);
if (blob.type === 'text/xml') {
blob = new Blob([blob], { type: 'video/mp4' });
}
uri = URL.createObjectURL(blob);
}
if (!shaka.Player.isBrowserSupported()) { // primarily iOS WebKit
this.videoElement.setAttribute('src', uri);
if (!this._paused) {
this.requestPlay();
}
} else {
this.player.load(uri)
.then(() => {
if (!this._paused) {
this.requestPlay();
}
})
.catch(this.onError);
}
}
set volume(value: number) {
if (value === 0) {
this.muted = true;
} else {
this.videoElement.volume = value;
this.muted = false;
}
}
onEnd = () => {
this.onProgress();
this.sendEvent('topVideoEnd', null);
this.stopProgressTimer();
}
onError = error => {
console.warn('topVideoError', error);
}
onLoad = () => {
// height & width are safe with audio, will be 0
const height = this.videoElement.videoHeight;
const width = this.videoElement.videoWidth;
const payload = {
currentPosition: this.videoElement.currentTime,
duration: this.videoElement.duration,
naturalSize: {
width,
height,
orientation: width >= height ? 'landscape' : 'portrait',
},
};
this.sendEvent('topVideoLoad', payload);
}
onReadyForDisplay = () => {
this.sendEvent('onReadyForDisplay');
}
onLoadStart = () => {
const src = this.videoElement.currentSrc;
const payload = {
isNetwork: !src.match(/^https?:\/\/localhost/), // require is served from localhost
uri: this.videoElement.currentSrc,
};
this.sendEvent('topVideoLoadStart', payload);
}
onPause = () => {
this.stopProgressTimer();
}
onPlay = () => {
this.startProgressTimer();
}
onProgress = () => {
const payload = {
currentTime: this.videoElement.currentTime,
seekableDuration: this.videoElement.duration,
};
this.sendEvent('topVideoProgress', payload);
}
onRejectedAutoplay = () => {
this.sendEvent('topVideoRejectedAutoplay', null);
}
requestPlay() {
const playPromise = this.videoElement.play();
if (playPromise) {
playPromise
.then(() => {})
.catch(e => {
/* This is likely one of:
* name: NotAllowedError - autoplay is not supported
* name: NotSupportedError - format is not supported
*/
this.onError({ code: e.name, message: e.message });
});
}
}
sendEvent(eventName, payload) {
const event = new RCTVideoEvent(eventName, this.reactTag, 0, payload);
this.eventDispatcher.sendEvent(event);
}
startProgressTimer() {
if (!this.progressTimer && this._progressUpdateInterval) {
this.onProgress();
this.progressTimer = setInterval(this.onProgress, this._progressUpdateInterval);
}
}
stopProgressTimer() {
if (this.progressTimer) {
clearInterval(this.progressTimer);
this.progressTimer = null;
}
}
}
customElements.define('rct-video', RCTVideo);
export default RCTVideo;

View File

@ -1,56 +0,0 @@
// import { RCTEvent } from "react-native-dom";
interface RCTEvent {
viewTag: number;
eventName: string;
coalescingKey: number;
canCoalesce(): boolean;
coalesceWithEvent(event: RCTEvent): RCTEvent;
moduleDotMethod(): string;
arguments(): Array<any>;
}
export default class RCTVideoEvent implements RCTEvent {
viewTag: number;
eventName: string;
coalescingKey: number;
constructor(
eventName: string,
reactTag: number,
coalescingKey: number,
data: ?Object
) {
this.viewTag = reactTag;
this.eventName = eventName;
this.coalescingKey = coalescingKey;
this.data = data;
}
canCoalesce(): boolean {
return false;
}
coalesceWithEvent(event: RCTEvent): RCTEvent {
return;
}
moduleDotMethod(): string {
return 'RCTEventEmitter.receiveEvent';
}
arguments(): Array<any> {
const args = [
this.viewTag,
this.eventName,
this.data,
];
return args;
}
coalescingKey(): number {
return this.coalescingKey;
}
}

View File

@ -1,87 +0,0 @@
// @flow
import { RCTViewManager } from 'react-native-dom';
import RCTVideo from './RCTVideo';
import resizeModes from './resizeModes';
import type { VideoSource } from './types';
class RCTVideoManager extends RCTViewManager {
static moduleName = 'RCTVideoManager';
view() {
return new RCTVideo(this.bridge);
}
describeProps() {
return super
.describeProps()
.addBooleanProp('controls', this.setControls)
.addStringProp('id', this.setId)
.addBooleanProp('muted', this.setMuted)
.addBooleanProp('paused', this.setPaused)
.addNumberProp('progressUpdateInterval', this.setProgressUpdateInterval)
.addBooleanProp('rate', this.setRate)
.addBooleanProp('repeat', this.setRepeat)
.addNumberProp('resizeMode', this.setResizeMode)
.addNumberProp('seek', this.setSeek)
.addObjectProp('src', this.setSource)
.addNumberProp('volume', this.setVolume)
.addDirectEvent('onVideoEnd')
.addDirectEvent('onVideoError')
.addDirectEvent('onVideoLoad')
.addDirectEvent('onVideoLoadStart')
.addDirectEvent('onVideoProgress');
}
dismissFullscreenPlayer() {
// not currently working
}
presentFullscreenPlayer() {
// not currently working
}
setControls(view: RCTVideo, value: boolean) {
view.controls = value;
}
setId(view: RCTVideo, value: string) {
view.id = value;
}
setMuted(view: RCTVideo, value: boolean) {
view.muted = value;
}
setPaused(view: RCTVideo, value: boolean) {
view.paused = value;
}
setRate(view: RCTVideo, value: number) {
view.rate = value;
}
setRepeat(view: RCTVideo, value: boolean) {
view.repeat = value;
}
setResizeMode(view: RCTVideo, value: number) {
view.resizeMode = value;
}
setSeek(view: RCTVideo, value: number) {
view.seek = value;
}
setSource(view: RCTVideo, value: VideoSource) {
view.source = value;
}
constantsToExport() {
return { ...resizeModes };
}
}
export default RCTVideoManager;

View File

@ -1,3 +0,0 @@
// @flow
module.exports = require('./RCTVideoManager');

View File

@ -1,8 +0,0 @@
// @flow
export default {
ScaleNone: 0,
ScaleToFill: 1,
ScaleAspectFit: 2,
ScaleAspectFill: 3,
};

View File

@ -1,10 +0,0 @@
// @flow
export type VideoSource = {
uri: string,
type: string,
mainVer: number,
patchVer: number,
isNetwork: boolean,
isAsset: boolean,
};

View File

@ -1,5 +1,5 @@
rootProject.name = 'VideoPlayer'
apply from: file("../node_modules/@react-native-community/cli-platform-android/native_modules.gradle"); applyNativeModulesSettingsGradle(settings)
include ':react-native-video'
project(':react-native-video').projectDir = new File(rootProject.projectDir, '../node_modules/react-native-video/android-exoplayer')
project(':react-native-video').projectDir = new File(rootProject.projectDir, '../node_modules/react-native-video/android')
include ':app'

View File

@ -21,7 +21,7 @@ class RCTVideo: UIView, RCTVideoPlayerViewControllerDelegate, RCTPlayerObserverH
private var _localSourceEncryptionKeyScheme:String?
/* Required to publish events */
private var _eventDispatcher:RCTEventDispatcher?
private var _eventDispatcher:RCTEventDispatcherProtocol?
private var _videoLoadStarted:Bool = false
private var _pendingSeek:Bool = false
@ -95,7 +95,7 @@ class RCTVideo: UIView, RCTVideoPlayerViewControllerDelegate, RCTPlayerObserverH
@objc var onRestoreUserInterfaceForPictureInPictureStop: RCTDirectEventBlock?
@objc var onGetLicense: RCTDirectEventBlock?
init(eventDispatcher:RCTEventDispatcher!) {
init(eventDispatcher:RCTEventDispatcherProtocol!) {
super.init(frame: CGRect(x: 0, y: 0, width: 100, height: 100))
_eventDispatcher = eventDispatcher
@ -1043,6 +1043,7 @@ class RCTVideo: UIView, RCTVideoPlayerViewControllerDelegate, RCTPlayerObserverH
item.seek(to: CMTime.zero)
self.applyModifiers()
} else {
self.setPaused(true);
_playerObserver.removePlayerTimeObserver()
}
}

View File

@ -1,6 +1,6 @@
{
"name": "react-native-video",
"version": "6.0.0-alpha.0",
"version": "6.0.0-alpha.1",
"description": "A <Video /> element for react-native",
"main": "Video.js",
"license": "MIT",
@ -13,26 +13,20 @@
"@react-native-community/eslint-config": "^0.0.5",
"eslint": "^6.5.1",
"react": "16.9.0",
"react-dom": "16.9.0",
"react-hot-loader": "^4.12.19",
"react-native": "0.61.5",
"react-native-dom": "^0.5.0",
"react-native-windows": "^0.61.0-0"
},
"dependencies": {
"deprecated-react-native-prop-types": "^2.2.0",
"keymirror": "^0.1.1",
"prop-types": "^15.7.2",
"shaka-player": "^3.3.2"
"prop-types": "^15.7.2"
},
"scripts": {
"lint": "yarn eslint .",
"xbasic": "yarn --cwd examples/basic"
},
"files": [
"android-exoplayer",
"android",
"dom",
"ios",
"windows",
"FilterType.js",