diff --git a/apps/native-component-list/src/screens/Video/VideoScreen.tsx b/apps/native-component-list/src/screens/Video/VideoScreen.tsx
index 3ac652dc5b70e..5fbae08a08e16 100644
--- a/apps/native-component-list/src/screens/Video/VideoScreen.tsx
+++ b/apps/native-component-list/src/screens/Video/VideoScreen.tsx
@@ -2,7 +2,7 @@ import Slider from '@react-native-community/slider';
import { Picker } from '@react-native-picker/picker';
import SegmentedControl from '@react-native-segmented-control/segmented-control';
import { Platform } from 'expo-modules-core';
-import { useVideoPlayer, VideoView, VideoSource } from 'expo-video';
+import { useVideoPlayer, VideoView, VideoSource, VideoPlayerEvents } from 'expo-video';
import React, { useCallback, useEffect, useRef } from 'react';
import { PixelRatio, ScrollView, StyleSheet, Text, View } from 'react-native';
@@ -30,7 +30,14 @@ const androidDrmSource: VideoSource = {
const videoLabels: string[] = ['Big Buck Bunny', 'Elephants Dream'];
const videoSources: VideoSource[] = [bigBuckBunnySource, elephantsDreamSource];
const playbackRates: number[] = [0.25, 0.5, 1, 1.5, 2, 16];
-
+const eventsToListen: (keyof VideoPlayerEvents)[] = [
+ 'statusChange',
+ 'playingChange',
+ 'playbackRateChange',
+ 'volumeChange',
+ 'playToEnd',
+ 'sourceChange',
+];
if (Platform.OS === 'android') {
videoLabels.push('Tears of Steel (DRM protected)');
videoSources.push(androidDrmSource);
@@ -47,11 +54,18 @@ export default function VideoScreen() {
const [staysActiveInBackground, setStaysActiveInBackground] = React.useState(false);
const [loop, setLoop] = React.useState(false);
const [playbackRateIndex, setPlaybackRateIndex] = React.useState(2);
- const [shouldCorrectPitch, setCorrectsPitch] = React.useState(true);
+ const [preservePitch, setPreservePitch] = React.useState(true);
const [volume, setVolume] = React.useState(1);
const [currentSource, setCurrentSource] = React.useState(videoSources[0]);
+ const [logEvents, setLogEvents] = React.useState(false);
- const player = useVideoPlayer(currentSource);
+ const player = useVideoPlayer(currentSource, (player) => {
+ player.volume = volume;
+ player.loop = loop;
+ player.preservesPitch = preservePitch;
+ player.staysActiveInBackground = staysActiveInBackground;
+ player.play();
+ });
const enterFullscreen = useCallback(() => {
ref.current?.enterFullscreen();
@@ -102,17 +116,30 @@ export default function VideoScreen() {
);
const updatePreservesPitch = useCallback(
- (correctPitch: boolean) => {
- player.preservesPitch = correctPitch;
- setCorrectsPitch(correctPitch);
+ (preservesPitch: boolean) => {
+ player.preservesPitch = preservesPitch;
+ setPreservePitch(preservesPitch);
},
[player]
);
useEffect(() => {
- player.play();
- player.preservesPitch = shouldCorrectPitch;
- }, [player]);
+ if (logEvents) {
+ eventsToListen.forEach((eventName) => {
+ player.addListener(eventName, (newValue: any, _: any, error: any) => {
+ console.log(
+ `${eventName}: ${JSON.stringify(newValue)} ${(error && JSON.stringify(error)) ?? ''}`
+ );
+ });
+ });
+ }
+
+ return () => {
+ eventsToListen.forEach((eventName) => {
+ player.removeAllListeners(eventName);
+ });
+ };
+ }, [logEvents, player]);
return (
@@ -234,11 +261,18 @@ export default function VideoScreen() {
+
diff --git a/packages/expo-modules-core/android/src/main/java/expo/modules/kotlin/sharedobjects/SharedObject.kt b/packages/expo-modules-core/android/src/main/java/expo/modules/kotlin/sharedobjects/SharedObject.kt
index 214db20923fa8..520934a485bc3 100644
--- a/packages/expo-modules-core/android/src/main/java/expo/modules/kotlin/sharedobjects/SharedObject.kt
+++ b/packages/expo-modules-core/android/src/main/java/expo/modules/kotlin/sharedobjects/SharedObject.kt
@@ -26,12 +26,12 @@ open class SharedObject(appContext: AppContext? = null) {
)
}
- fun sendEvent(eventName: String, vararg args: Any) {
+ fun sendEvent(eventName: String, vararg args: Any?) {
val jsThis = getJavaScriptObject() ?: return
try {
jsThis.getProperty("emit")
- .getFunction()
+ .getFunction()
.invoke(
eventName,
*args,
diff --git a/packages/expo-video/CHANGELOG.md b/packages/expo-video/CHANGELOG.md
index baffcc06d342b..b6441d9e3f93d 100644
--- a/packages/expo-video/CHANGELOG.md
+++ b/packages/expo-video/CHANGELOG.md
@@ -6,6 +6,7 @@
### 🎉 New features
+- Add support for events on Android and iOS. ([#27632](https://github.com/expo/expo/pull/27632) by [@behenate](https://github.com/behenate))
- Add support for `loop`, `playbackRate`, `preservesPitch` and `currentTime` properties. ([#27367](https://github.com/expo/expo/pull/27367) by [@behenate](https://github.com/behenate))
- Add background playback support. ([#27110](https://github.com/expo/expo/pull/27110) by [@behenate](https://github.com/behenate))
- Add DRM support for Android and iOS. ([#26465](https://github.com/expo/expo/pull/26465) by [@behenate](https://github.com/behenate))
diff --git a/packages/expo-video/android/src/main/java/expo/modules/video/VideoExceptions.kt b/packages/expo-video/android/src/main/java/expo/modules/video/VideoExceptions.kt
index 38e24b157e64e..acc996cfcdcc0 100644
--- a/packages/expo-video/android/src/main/java/expo/modules/video/VideoExceptions.kt
+++ b/packages/expo-video/android/src/main/java/expo/modules/video/VideoExceptions.kt
@@ -20,3 +20,6 @@ internal class PictureInPictureUnsupportedException :
internal class UnsupportedDRMTypeException(type: DRMType) :
CodedException("DRM type `$type` is not supported on Android")
+
+internal class PlaybackException(reason: String?, cause: Throwable? = null) :
+ CodedException("A playback exception has occurred: ${reason ?: "reason unknown"}", cause)
diff --git a/packages/expo-video/android/src/main/java/expo/modules/video/VideoManager.kt b/packages/expo-video/android/src/main/java/expo/modules/video/VideoManager.kt
index 1a7db4bef0e30..4be43a09ca110 100644
--- a/packages/expo-video/android/src/main/java/expo/modules/video/VideoManager.kt
+++ b/packages/expo-video/android/src/main/java/expo/modules/video/VideoManager.kt
@@ -1,7 +1,10 @@
package expo.modules.video
import androidx.annotation.OptIn
+import androidx.media3.common.MediaItem
import androidx.media3.common.util.UnstableApi
+import expo.modules.video.records.VideoSource
+import java.lang.ref.WeakReference
// Helper class used to keep track of all existing VideoViews and VideoPlayers
@OptIn(UnstableApi::class)
@@ -14,6 +17,9 @@ object VideoManager {
// Keeps track of all existing VideoPlayers, and whether they are attached to a VideoView
private var videoPlayersToVideoViews = mutableMapOf>()
+ // Keeps track of all existing MediaItems and their corresponding VideoSources. Used for recognizing source of MediaItems.
+ private var mediaItemsToVideoSources = mutableMapOf>()
+
fun registerVideoView(videoView: VideoView) {
videoViews[videoView.id] = videoView
}
@@ -34,6 +40,17 @@ object VideoManager {
videoPlayersToVideoViews.remove(videoPlayer)
}
+ fun registerVideoSourceToMediaItem(mediaItem: MediaItem, videoSource: VideoSource) {
+ mediaItemsToVideoSources[mediaItem.mediaId] = WeakReference(videoSource)
+ }
+
+ fun getVideoSourceFromMediaItem(mediaItem: MediaItem?): VideoSource? {
+ if (mediaItem == null) {
+ return null
+ }
+ return mediaItemsToVideoSources[mediaItem.mediaId]?.get()
+ }
+
fun onVideoPlayerAttachedToView(videoPlayer: VideoPlayer, videoView: VideoView) {
if (videoPlayersToVideoViews[videoPlayer]?.contains(videoView) == true) {
return
diff --git a/packages/expo-video/android/src/main/java/expo/modules/video/VideoModule.kt b/packages/expo-video/android/src/main/java/expo/modules/video/VideoModule.kt
index 26a3367b4f5ed..428ad14490d75 100644
--- a/packages/expo-video/android/src/main/java/expo/modules/video/VideoModule.kt
+++ b/packages/expo-video/android/src/main/java/expo/modules/video/VideoModule.kt
@@ -139,7 +139,9 @@ class VideoModule : Module() {
Class(VideoPlayer::class) {
Constructor { source: VideoSource ->
- VideoPlayer(activity.applicationContext, appContext, source.toMediaItem())
+ val mediaItem = source.toMediaItem()
+ VideoManager.registerVideoSourceToMediaItem(mediaItem, source)
+ VideoPlayer(activity.applicationContext, appContext, mediaItem)
}
Property("playing")
@@ -147,11 +149,6 @@ class VideoModule : Module() {
ref.playing
}
- Property("isLoading")
- .get { ref: VideoPlayer ->
- ref.isLoading
- }
-
Property("muted")
.get { ref: VideoPlayer ->
ref.muted
@@ -209,6 +206,11 @@ class VideoModule : Module() {
}
}
+ Property("status")
+ .get { ref: VideoPlayer ->
+ ref.status
+ }
+
Property("staysActiveInBackground")
.get { ref: VideoPlayer ->
ref.staysActiveInBackground
@@ -249,9 +251,11 @@ class VideoModule : Module() {
} else {
VideoSource(source.get(String::class))
}
+ val mediaItem = videoSource.toMediaItem()
+ VideoManager.registerVideoSourceToMediaItem(mediaItem, videoSource)
appContext.mainQueue.launch {
- ref.player.setMediaItem(videoSource.toMediaItem())
+ ref.player.setMediaItem(mediaItem)
}
}
diff --git a/packages/expo-video/android/src/main/java/expo/modules/video/VideoPlayer.kt b/packages/expo-video/android/src/main/java/expo/modules/video/VideoPlayer.kt
index 7c015f695a4a6..f4e29e4675b57 100644
--- a/packages/expo-video/android/src/main/java/expo/modules/video/VideoPlayer.kt
+++ b/packages/expo-video/android/src/main/java/expo/modules/video/VideoPlayer.kt
@@ -10,6 +10,7 @@ import android.os.IBinder
import android.util.Log
import android.view.SurfaceView
import androidx.media3.common.MediaItem
+import androidx.media3.common.PlaybackException
import androidx.media3.common.PlaybackParameters
import androidx.media3.common.Player
import androidx.media3.common.Timeline
@@ -20,6 +21,10 @@ import androidx.media3.session.MediaSessionService
import androidx.media3.ui.PlayerView
import expo.modules.kotlin.AppContext
import expo.modules.kotlin.sharedobjects.SharedObject
+import expo.modules.video.enums.PlayerStatus
+import expo.modules.video.enums.PlayerStatus.*
+import expo.modules.video.records.PlaybackError
+import expo.modules.video.records.VolumeEvent
import kotlinx.coroutines.launch
// https://developer.android.com/guide/topics/media/media3/getting-started/migration-guide#improvements_in_media3
@@ -36,7 +41,25 @@ class VideoPlayer(context: Context, appContext: AppContext, private val mediaIte
// We duplicate some properties of the player, because we don't want to always use the mainQueue to access them.
var playing = false
- var isLoading = true
+ set(value) {
+ if (field != value) {
+ sendEventOnJSThread("playingChange", value, field)
+ }
+ field = value
+ }
+
+ var status: PlayerStatus = IDLE
+
+ var currentMediaItem: MediaItem? = null
+ set(newMediaItem) {
+ if (field != newMediaItem) {
+ val oldVideoSource = VideoManager.getVideoSourceFromMediaItem(field)
+ val newVideoSource = VideoManager.getVideoSourceFromMediaItem(newMediaItem)
+
+ sendEventOnJSThread("sourceChange", newVideoSource, oldVideoSource)
+ }
+ field = newMediaItem
+ }
// Volume of the player if there was no mute applied.
var userVolume = 1f
@@ -44,7 +67,7 @@ class VideoPlayer(context: Context, appContext: AppContext, private val mediaIte
var staysActiveInBackground = false
var preservesPitch = false
set(preservesPitch) {
- applyPitchCorrection()
+ playbackParameters = applyPitchCorrection(playbackParameters)
field = preservesPitch
}
@@ -56,21 +79,29 @@ class VideoPlayer(context: Context, appContext: AppContext, private val mediaIte
set(volume) {
if (player.volume == volume) return
player.volume = if (muted) 0f else volume
+ sendEventOnJSThread("volumeChange", VolumeEvent(volume, muted), VolumeEvent(field, muted))
field = volume
}
var muted = false
set(muted) {
+ if (field == muted) return
+ sendEventOnJSThread("volumeChange", VolumeEvent(volume, muted), VolumeEvent(volume, field))
+ player.volume = if (muted) 0f else userVolume
field = muted
- volume = if (muted) 0f else userVolume
}
var playbackParameters: PlaybackParameters = PlaybackParameters.DEFAULT
- set(value) {
- if (player.playbackParameters == value) return
- player.playbackParameters = value
- field = value
- applyPitchCorrection()
+ set(newPlaybackParameters) {
+ if (playbackParameters.speed != newPlaybackParameters.speed) {
+ sendEventOnJSThread("playbackRateChange", newPlaybackParameters.speed, playbackParameters.speed)
+ }
+ val pitchCorrectedPlaybackParameters = applyPitchCorrection(newPlaybackParameters)
+ field = pitchCorrectedPlaybackParameters
+
+ if (player.playbackParameters != pitchCorrectedPlaybackParameters) {
+ player.playbackParameters = pitchCorrectedPlaybackParameters
+ }
}
private val playerListener = object : Player.Listener {
@@ -82,8 +113,20 @@ class VideoPlayer(context: Context, appContext: AppContext, private val mediaIte
this@VideoPlayer.timeline = timeline
}
- override fun onIsLoadingChanged(isLoading: Boolean) {
- this@VideoPlayer.isLoading = isLoading
+ override fun onMediaItemTransition(mediaItem: MediaItem?, reason: Int) {
+ this@VideoPlayer.currentMediaItem = mediaItem
+ if (reason == Player.MEDIA_ITEM_TRANSITION_REASON_REPEAT) {
+ sendEventOnJSThread("playToEnd")
+ }
+ super.onMediaItemTransition(mediaItem, reason)
+ }
+
+ override fun onPlaybackStateChanged(@Player.State playbackState: Int) {
+ if (playbackState == Player.STATE_IDLE && player.playerError != null) {
+ return
+ }
+ setStatus(playerStateToPlayerStatus(playbackState), null)
+ super.onPlaybackStateChanged(playbackState)
}
override fun onVolumeChanged(volume: Float) {
@@ -94,6 +137,16 @@ class VideoPlayer(context: Context, appContext: AppContext, private val mediaIte
this@VideoPlayer.playbackParameters = playbackParameters
super.onPlaybackParametersChanged(playbackParameters)
}
+
+ override fun onPlayerErrorChanged(error: PlaybackException?) {
+ error?.let {
+ setStatus(ERROR, error)
+ } ?: run {
+ setStatus(playerStateToPlayerStatus(player.playbackState), null)
+ }
+
+ super.onPlayerErrorChanged(error)
+ }
}
init {
@@ -167,9 +220,47 @@ class VideoPlayer(context: Context, appContext: AppContext, private val mediaIte
player.prepare()
}
- private fun applyPitchCorrection() {
+ private fun applyPitchCorrection(playbackParameters: PlaybackParameters): PlaybackParameters {
val speed = playbackParameters.speed
val pitch = if (preservesPitch) 1f else speed
- playbackParameters = PlaybackParameters(speed, pitch)
+ return PlaybackParameters(speed, pitch)
+ }
+
+ private fun playerStateToPlayerStatus(@Player.State state: Int): PlayerStatus {
+ return when (state) {
+ Player.STATE_IDLE -> IDLE
+ Player.STATE_BUFFERING -> LOADING
+ Player.STATE_READY -> READY_TO_PLAY
+ Player.STATE_ENDED -> {
+ // When an error occurs, the player state changes to ENDED.
+ if (player.playerError != null) {
+ ERROR
+ } else {
+ IDLE
+ }
+ }
+
+ else -> IDLE
+ }
+ }
+ private fun setStatus(status: PlayerStatus, error: PlaybackException?) {
+ val playbackError = error?.let {
+ PlaybackError(it)
+ }
+
+ if (playbackError == null && player.playbackState == Player.STATE_ENDED) {
+ sendEventOnJSThread("playToEnd")
+ }
+
+ if (this.status != status) {
+ sendEventOnJSThread("statusChange", status.value, this.status.value, playbackError)
+ }
+ this.status = status
+ }
+
+ private fun sendEventOnJSThread(eventName: String, vararg args: Any?) {
+ appContext?.executeOnJavaScriptThread {
+ sendEvent(eventName, *args)
+ }
}
}
diff --git a/packages/expo-video/android/src/main/java/expo/modules/video/enums/DRMType.kt b/packages/expo-video/android/src/main/java/expo/modules/video/enums/DRMType.kt
index 7b44995e3abbc..c97e5ac61c601 100644
--- a/packages/expo-video/android/src/main/java/expo/modules/video/enums/DRMType.kt
+++ b/packages/expo-video/android/src/main/java/expo/modules/video/enums/DRMType.kt
@@ -5,7 +5,7 @@ import expo.modules.kotlin.types.Enumerable
import expo.modules.video.UnsupportedDRMTypeException
import java.util.UUID
-internal enum class DRMType(val value: String) : Enumerable {
+enum class DRMType(val value: String) : Enumerable {
CLEARKEY("clearkey"),
FAIRPLAY("fairplay"),
PLAYREADY("playready"),
diff --git a/packages/expo-video/android/src/main/java/expo/modules/video/enums/PlayerStatus.kt b/packages/expo-video/android/src/main/java/expo/modules/video/enums/PlayerStatus.kt
new file mode 100644
index 0000000000000..c57dee032b8ef
--- /dev/null
+++ b/packages/expo-video/android/src/main/java/expo/modules/video/enums/PlayerStatus.kt
@@ -0,0 +1,10 @@
+package expo.modules.video.enums
+
+import expo.modules.kotlin.types.Enumerable
+
+enum class PlayerStatus(val value: String) : Enumerable {
+ IDLE("idle"),
+ LOADING("loading"),
+ READY_TO_PLAY("readyToPlay"),
+ ERROR("error")
+}
diff --git a/packages/expo-video/android/src/main/java/expo/modules/video/records/DRMOptions.kt b/packages/expo-video/android/src/main/java/expo/modules/video/records/DRMOptions.kt
index e50fdebff710e..f01c7f02e4b96 100644
--- a/packages/expo-video/android/src/main/java/expo/modules/video/records/DRMOptions.kt
+++ b/packages/expo-video/android/src/main/java/expo/modules/video/records/DRMOptions.kt
@@ -6,7 +6,7 @@ import expo.modules.kotlin.records.Record
import expo.modules.video.enums.DRMType
import java.io.Serializable
-internal class DRMOptions(
+class DRMOptions(
@Field var type: DRMType = DRMType.WIDEVINE,
@Field var licenseServer: String? = null,
@Field var headers: Map? = null,
diff --git a/packages/expo-video/android/src/main/java/expo/modules/video/records/PlaybackError.kt b/packages/expo-video/android/src/main/java/expo/modules/video/records/PlaybackError.kt
new file mode 100644
index 0000000000000..6f226fb0da7f5
--- /dev/null
+++ b/packages/expo-video/android/src/main/java/expo/modules/video/records/PlaybackError.kt
@@ -0,0 +1,19 @@
+package expo.modules.video.records
+
+import androidx.media3.common.PlaybackException
+import expo.modules.kotlin.records.Field
+import expo.modules.kotlin.records.Record
+import java.io.Serializable
+
+class PlaybackError(
+ @Field var message: String? = null
+) : Record, Serializable {
+ constructor(exception: PlaybackException) : this(errorMessageFromException(exception))
+
+ companion object {
+ private fun errorMessageFromException(exception: PlaybackException): String {
+ val reason = "${exception.localizedMessage} ${exception.cause?.localizedMessage ?: ""}"
+ return "A playback exception has occurred: $reason"
+ }
+ }
+}
diff --git a/packages/expo-video/android/src/main/java/expo/modules/video/records/VideoSource.kt b/packages/expo-video/android/src/main/java/expo/modules/video/records/VideoSource.kt
index a799f9c2531c3..d0fd7f9ae711a 100644
--- a/packages/expo-video/android/src/main/java/expo/modules/video/records/VideoSource.kt
+++ b/packages/expo-video/android/src/main/java/expo/modules/video/records/VideoSource.kt
@@ -6,10 +6,19 @@ import expo.modules.kotlin.records.Record
import expo.modules.video.UnsupportedDRMTypeException
import java.io.Serializable
-internal class VideoSource(
+class VideoSource(
@Field var uri: String? = null,
@Field var drm: DRMOptions? = null
) : Record, Serializable {
+ private fun toMediaId(): String {
+ return "uri:${this.uri}" +
+ "DrmType:${this.drm?.type}" +
+ "DrmLicenseServer:${this.drm?.licenseServer}" +
+ "DrmMultiKey:${this.drm?.multiKey}" +
+ "DRMHeadersKeys:${this.drm?.headers?.keys?.joinToString {it}}}" +
+ "DRMHeadersValues:${this.drm?.headers?.values?.joinToString {it}}}"
+ }
+
fun toMediaItem() = MediaItem
.Builder()
.apply {
diff --git a/packages/expo-video/android/src/main/java/expo/modules/video/records/VolumeEvent.kt b/packages/expo-video/android/src/main/java/expo/modules/video/records/VolumeEvent.kt
new file mode 100644
index 0000000000000..0e25af1c6b398
--- /dev/null
+++ b/packages/expo-video/android/src/main/java/expo/modules/video/records/VolumeEvent.kt
@@ -0,0 +1,10 @@
+package expo.modules.video.records
+
+import expo.modules.kotlin.records.Field
+import expo.modules.kotlin.records.Record
+import java.io.Serializable
+
+internal class VolumeEvent(
+ @Field var volume: Float? = null,
+ @Field var muted: Boolean? = null
+) : Record, Serializable
diff --git a/packages/expo-video/build/NativeVideoModule.web.d.ts b/packages/expo-video/build/NativeVideoModule.web.d.ts
new file mode 100644
index 0000000000000..a5545849f1af0
--- /dev/null
+++ b/packages/expo-video/build/NativeVideoModule.web.d.ts
@@ -0,0 +1,3 @@
+declare const _default: () => void;
+export default _default;
+//# sourceMappingURL=NativeVideoModule.web.d.ts.map
\ No newline at end of file
diff --git a/packages/expo-video/build/NativeVideoModule.web.d.ts.map b/packages/expo-video/build/NativeVideoModule.web.d.ts.map
new file mode 100644
index 0000000000000..4b48022f87b79
--- /dev/null
+++ b/packages/expo-video/build/NativeVideoModule.web.d.ts.map
@@ -0,0 +1 @@
+{"version":3,"file":"NativeVideoModule.web.d.ts","sourceRoot":"","sources":["../src/NativeVideoModule.web.ts"],"names":[],"mappings":";AAAA,wBAAwB"}
\ No newline at end of file
diff --git a/packages/expo-video/build/NativeVideoModule.web.js b/packages/expo-video/build/NativeVideoModule.web.js
new file mode 100644
index 0000000000000..f2c1d2eb52c98
--- /dev/null
+++ b/packages/expo-video/build/NativeVideoModule.web.js
@@ -0,0 +1,2 @@
+export default () => { };
+//# sourceMappingURL=NativeVideoModule.web.js.map
\ No newline at end of file
diff --git a/packages/expo-video/build/NativeVideoModule.web.js.map b/packages/expo-video/build/NativeVideoModule.web.js.map
new file mode 100644
index 0000000000000..ee9770038b9ed
--- /dev/null
+++ b/packages/expo-video/build/NativeVideoModule.web.js.map
@@ -0,0 +1 @@
+{"version":3,"file":"NativeVideoModule.web.js","sourceRoot":"","sources":["../src/NativeVideoModule.web.ts"],"names":[],"mappings":"AAAA,eAAe,GAAG,EAAE,GAAE,CAAC,CAAC","sourcesContent":["export default () => {};\n"]}
\ No newline at end of file
diff --git a/packages/expo-video/build/VideoView.d.ts b/packages/expo-video/build/VideoView.d.ts
index 09899951611dd..dd4d18face94e 100644
--- a/packages/expo-video/build/VideoView.d.ts
+++ b/packages/expo-video/build/VideoView.d.ts
@@ -1,6 +1,6 @@
import { ReactNode, PureComponent } from 'react';
import { VideoPlayer, VideoSource, VideoViewProps } from './VideoView.types';
-export declare function useVideoPlayer(source: VideoSource): VideoPlayer;
+export declare function useVideoPlayer(source: VideoSource, setup?: (player: VideoPlayer) => void): VideoPlayer;
/**
* Returns whether the current device supports Picture in Picture (PiP) mode.
* @returns A `boolean` which is `true` if the device supports PiP mode, and `false` otherwise.
diff --git a/packages/expo-video/build/VideoView.d.ts.map b/packages/expo-video/build/VideoView.d.ts.map
index 724781ee98213..30900ea2ccad4 100644
--- a/packages/expo-video/build/VideoView.d.ts.map
+++ b/packages/expo-video/build/VideoView.d.ts.map
@@ -1 +1 @@
-{"version":3,"file":"VideoView.d.ts","sourceRoot":"","sources":["../src/VideoView.tsx"],"names":[],"mappings":"AACA,OAAO,EACL,SAAS,EACT,aAAa,EAMd,MAAM,OAAO,CAAC;AAIf,OAAO,EAAE,WAAW,EAAE,WAAW,EAAE,cAAc,EAAE,MAAM,mBAAmB,CAAC;AAE7E,wBAAgB,cAAc,CAAC,MAAM,EAAE,WAAW,GAAG,WAAW,CAO/D;AAED;;;;;GAKG;AACH,wBAAgB,2BAA2B,IAAI,OAAO,CAAC,OAAO,CAAC,CAE9D;AAED,qBAAa,SAAU,SAAQ,aAAa,CAAC,cAAc,CAAC;IAC1D,SAAS,iCAAoB;IAE7B,OAAO,CAAC,MAAM,EAAE,WAAW;IAQ3B,eAAe;IAIf,cAAc;IAId;;;;;OAKG;IACH,qBAAqB;IAIrB;;;;OAIG;IACH,oBAAoB;IAIpB,MAAM,IAAI,SAAS;CAMpB"}
\ No newline at end of file
+{"version":3,"file":"VideoView.d.ts","sourceRoot":"","sources":["../src/VideoView.tsx"],"names":[],"mappings":"AACA,OAAO,EACL,SAAS,EACT,aAAa,EAMd,MAAM,OAAO,CAAC;AAIf,OAAO,EAAE,WAAW,EAAE,WAAW,EAAE,cAAc,EAAE,MAAM,mBAAmB,CAAC;AAE7E,wBAAgB,cAAc,CAC5B,MAAM,EAAE,WAAW,EACnB,KAAK,CAAC,EAAE,CAAC,MAAM,EAAE,WAAW,KAAK,IAAI,GACpC,WAAW,CAQb;AAED;;;;;GAKG;AACH,wBAAgB,2BAA2B,IAAI,OAAO,CAAC,OAAO,CAAC,CAE9D;AAED,qBAAa,SAAU,SAAQ,aAAa,CAAC,cAAc,CAAC;IAC1D,SAAS,iCAAoB;IAE7B,OAAO,CAAC,MAAM,EAAE,WAAW;IAQ3B,eAAe;IAIf,cAAc;IAId;;;;;OAKG;IACH,qBAAqB;IAIrB;;;;OAIG;IACH,oBAAoB;IAIpB,MAAM,IAAI,SAAS;CAMpB"}
\ No newline at end of file
diff --git a/packages/expo-video/build/VideoView.js b/packages/expo-video/build/VideoView.js
index e3bc2a9177dd9..2f7b6adc4773c 100644
--- a/packages/expo-video/build/VideoView.js
+++ b/packages/expo-video/build/VideoView.js
@@ -1,9 +1,13 @@
import { PureComponent, createRef, useRef, useMemo, useEffect, } from 'react';
import NativeVideoModule from './NativeVideoModule';
import NativeVideoView from './NativeVideoView';
-export function useVideoPlayer(source) {
+export function useVideoPlayer(source, setup) {
const parsedSource = typeof source === 'string' ? { uri: source } : source;
- return useReleasingSharedObject(() => new NativeVideoModule.VideoPlayer(parsedSource), [JSON.stringify(parsedSource)]);
+ return useReleasingSharedObject(() => {
+ const player = new NativeVideoModule.VideoPlayer(parsedSource);
+ setup?.(player);
+ return player;
+ }, [JSON.stringify(parsedSource)]);
}
/**
* Returns whether the current device supports Picture in Picture (PiP) mode.
diff --git a/packages/expo-video/build/VideoView.js.map b/packages/expo-video/build/VideoView.js.map
index 66ecf80f87c68..bb292fa4bbf4f 100644
--- a/packages/expo-video/build/VideoView.js.map
+++ b/packages/expo-video/build/VideoView.js.map
@@ -1 +1 @@
-{"version":3,"file":"VideoView.js","sourceRoot":"","sources":["../src/VideoView.tsx"],"names":[],"mappings":"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","sourcesContent":["import { SharedObject } from 'expo-modules-core';\nimport {\n ReactNode,\n PureComponent,\n DependencyList,\n createRef,\n useRef,\n useMemo,\n useEffect,\n} from 'react';\n\nimport NativeVideoModule from './NativeVideoModule';\nimport NativeVideoView from './NativeVideoView';\nimport { VideoPlayer, VideoSource, VideoViewProps } from './VideoView.types';\n\nexport function useVideoPlayer(source: VideoSource): VideoPlayer {\n const parsedSource = typeof source === 'string' ? { uri: source } : source;\n\n return useReleasingSharedObject(\n () => new NativeVideoModule.VideoPlayer(parsedSource),\n [JSON.stringify(parsedSource)]\n );\n}\n\n/**\n * Returns whether the current device supports Picture in Picture (PiP) mode.\n * @returns A `boolean` which is `true` if the device supports PiP mode, and `false` otherwise.\n * @platform android\n * @platform ios\n */\nexport function isPictureInPictureSupported(): Promise {\n return NativeVideoModule.isPictureInPictureSupported();\n}\n\nexport class VideoView extends PureComponent {\n nativeRef = createRef();\n\n replace(source: VideoSource) {\n if (typeof source === 'string') {\n this.nativeRef.current?.replace({ uri: source });\n return;\n }\n this.nativeRef.current?.replace(source);\n }\n\n enterFullscreen() {\n this.nativeRef.current?.enterFullscreen();\n }\n\n exitFullscreen() {\n this.nativeRef.current?.exitFullscreen();\n }\n\n /**\n * Enters Picture in Picture (PiP) mode. Throws an exception if the device does not support PiP.\n * > **Note:** Only one player can be in Picture in Picture (PiP) mode at a time.\n * @platform android\n * @platform ios 14+\n */\n startPictureInPicture() {\n return this.nativeRef.current?.startPictureInPicture();\n }\n\n /**\n * Exits Picture in Picture (PiP) mode.\n * @platform android\n * @platform ios 14+\n */\n stopPictureInPicture() {\n return this.nativeRef.current?.stopPictureInPicture();\n }\n\n render(): ReactNode {\n const { player, ...props } = this.props;\n const playerId = getPlayerId(player);\n\n return ;\n }\n}\n\n// Temporary solution to pass the shared object ID instead of the player object.\n// We can't really pass it as an object in the old architecture.\n// Technically we can in the new architecture, but it's not possible yet.\nfunction getPlayerId(player: number | VideoPlayer): number | null {\n if (player instanceof NativeVideoModule.VideoPlayer) {\n // @ts-expect-error\n return player.__expo_shared_object_id__;\n }\n if (typeof player === 'number') {\n return player;\n }\n return null;\n}\n\n/**\n * Returns a shared object, which is automatically cleaned up when the component is unmounted.\n */\nfunction useReleasingSharedObject(\n factory: () => T,\n dependencies: DependencyList\n): T {\n const objectRef = useRef(null);\n const isFastRefresh = useRef(false);\n const previousDependencies = useRef(dependencies);\n\n if (objectRef.current == null) {\n objectRef.current = factory();\n }\n\n const object = useMemo(() => {\n let newObject = objectRef.current;\n const dependenciesAreEqual =\n previousDependencies.current?.length === dependencies.length &&\n dependencies.every((value, index) => value === previousDependencies.current[index]);\n\n // If the dependencies have changed, release the previous object and create a new one, otherwise this has been called\n // because of a fast refresh, and we don't want to release the object.\n if (!newObject || !dependenciesAreEqual) {\n objectRef.current?.release();\n newObject = factory();\n objectRef.current = newObject;\n previousDependencies.current = dependencies;\n } else {\n isFastRefresh.current = true;\n }\n return newObject;\n }, dependencies);\n\n useEffect(() => {\n isFastRefresh.current = false;\n\n return () => {\n // This will be called on every fast refresh and on unmount, but we only want to release the object on unmount.\n if (!isFastRefresh.current && objectRef.current) {\n objectRef.current.release();\n }\n };\n }, []);\n\n return object;\n}\n"]}
\ No newline at end of file
+{"version":3,"file":"VideoView.js","sourceRoot":"","sources":["../src/VideoView.tsx"],"names":[],"mappings":"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","sourcesContent":["import { SharedObject } from 'expo-modules-core';\nimport {\n ReactNode,\n PureComponent,\n DependencyList,\n createRef,\n useRef,\n useMemo,\n useEffect,\n} from 'react';\n\nimport NativeVideoModule from './NativeVideoModule';\nimport NativeVideoView from './NativeVideoView';\nimport { VideoPlayer, VideoSource, VideoViewProps } from './VideoView.types';\n\nexport function useVideoPlayer(\n source: VideoSource,\n setup?: (player: VideoPlayer) => void\n): VideoPlayer {\n const parsedSource = typeof source === 'string' ? { uri: source } : source;\n\n return useReleasingSharedObject(() => {\n const player = new NativeVideoModule.VideoPlayer(parsedSource);\n setup?.(player);\n return player;\n }, [JSON.stringify(parsedSource)]);\n}\n\n/**\n * Returns whether the current device supports Picture in Picture (PiP) mode.\n * @returns A `boolean` which is `true` if the device supports PiP mode, and `false` otherwise.\n * @platform android\n * @platform ios\n */\nexport function isPictureInPictureSupported(): Promise {\n return NativeVideoModule.isPictureInPictureSupported();\n}\n\nexport class VideoView extends PureComponent {\n nativeRef = createRef();\n\n replace(source: VideoSource) {\n if (typeof source === 'string') {\n this.nativeRef.current?.replace({ uri: source });\n return;\n }\n this.nativeRef.current?.replace(source);\n }\n\n enterFullscreen() {\n this.nativeRef.current?.enterFullscreen();\n }\n\n exitFullscreen() {\n this.nativeRef.current?.exitFullscreen();\n }\n\n /**\n * Enters Picture in Picture (PiP) mode. Throws an exception if the device does not support PiP.\n * > **Note:** Only one player can be in Picture in Picture (PiP) mode at a time.\n * @platform android\n * @platform ios 14+\n */\n startPictureInPicture() {\n return this.nativeRef.current?.startPictureInPicture();\n }\n\n /**\n * Exits Picture in Picture (PiP) mode.\n * @platform android\n * @platform ios 14+\n */\n stopPictureInPicture() {\n return this.nativeRef.current?.stopPictureInPicture();\n }\n\n render(): ReactNode {\n const { player, ...props } = this.props;\n const playerId = getPlayerId(player);\n\n return ;\n }\n}\n\n// Temporary solution to pass the shared object ID instead of the player object.\n// We can't really pass it as an object in the old architecture.\n// Technically we can in the new architecture, but it's not possible yet.\nfunction getPlayerId(player: number | VideoPlayer): number | null {\n if (player instanceof NativeVideoModule.VideoPlayer) {\n // @ts-expect-error\n return player.__expo_shared_object_id__;\n }\n if (typeof player === 'number') {\n return player;\n }\n return null;\n}\n\n/**\n * Returns a shared object, which is automatically cleaned up when the component is unmounted.\n */\nfunction useReleasingSharedObject(\n factory: () => T,\n dependencies: DependencyList\n): T {\n const objectRef = useRef(null);\n const isFastRefresh = useRef(false);\n const previousDependencies = useRef(dependencies);\n\n if (objectRef.current == null) {\n objectRef.current = factory();\n }\n\n const object = useMemo(() => {\n let newObject = objectRef.current;\n const dependenciesAreEqual =\n previousDependencies.current?.length === dependencies.length &&\n dependencies.every((value, index) => value === previousDependencies.current[index]);\n\n // If the dependencies have changed, release the previous object and create a new one, otherwise this has been called\n // because of a fast refresh, and we don't want to release the object.\n if (!newObject || !dependenciesAreEqual) {\n objectRef.current?.release();\n newObject = factory();\n objectRef.current = newObject;\n previousDependencies.current = dependencies;\n } else {\n isFastRefresh.current = true;\n }\n return newObject;\n }, dependencies);\n\n useEffect(() => {\n isFastRefresh.current = false;\n\n return () => {\n // This will be called on every fast refresh and on unmount, but we only want to release the object on unmount.\n if (!isFastRefresh.current && objectRef.current) {\n objectRef.current.release();\n }\n };\n }, []);\n\n return object;\n}\n"]}
\ No newline at end of file
diff --git a/packages/expo-video/build/VideoView.types.d.ts b/packages/expo-video/build/VideoView.types.d.ts
index e599d341c7ca7..7d47366c63e96 100644
--- a/packages/expo-video/build/VideoView.types.d.ts
+++ b/packages/expo-video/build/VideoView.types.d.ts
@@ -3,7 +3,7 @@ import { ViewProps } from 'react-native';
/**
* A class that represents an instance of the video player.
*/
-export declare class VideoPlayer extends SharedObject {
+export declare class VideoPlayer extends SharedObject {
/**
* Boolean value whether the player is currently playing.
* > This property is get-only, use `play` and `pause` methods to control the playback.
@@ -43,6 +43,11 @@ export declare class VideoPlayer extends SharedObject {
* @default 1.0
*/
playbackRate: number;
+ /**
+ * Indicates the current status of the player.
+ * > This property is get-only
+ */
+ status: PlayerStatus;
/**
* Determines whether the player should continue playing after the app enters the background.
* @default false
@@ -73,11 +78,19 @@ export declare class VideoPlayer extends SharedObject {
}
/**
* Describes how a video should be scaled to fit in a container.
- * 'contain': The video maintains its aspect ratio and fits inside the container, with possible letterboxing/pillarboxing.
- * 'cover': The video maintains its aspect ratio and covers the entire container, potentially cropping some portions.
- * 'fill': The video stretches/squeezes to completely fill the container, potentially causing distortion.
+ * - `contain`: The video maintains its aspect ratio and fits inside the container, with possible letterboxing/pillarboxing.
+ * - `cover`: The video maintains its aspect ratio and covers the entire container, potentially cropping some portions.
+ * - `fill`: The video stretches/squeezes to completely fill the container, potentially causing distortion.
*/
type VideoContentFit = 'contain' | 'cover' | 'fill';
+/**
+ * Describes the current status of the player.
+ * - `idle`: The player is not playing or loading any videos.
+ * - `loading`: The player is loading video data from the provided source
+ * - `readyToPlay`: The player has loaded enough data to start playing or to continue playback.
+ * - `error`: The player has encountered an error while loading or playing the video.
+ */
+export type PlayerStatus = 'idle' | 'loading' | 'readyToPlay' | 'error';
export interface VideoViewProps extends ViewProps {
/**
* A player instance – use `useVideoPlayer()` to create one.
@@ -87,40 +100,40 @@ export interface VideoViewProps extends ViewProps {
* Determines whether native controls should be displayed or not.
* @default true
*/
- nativeControls: boolean | undefined;
+ nativeControls?: boolean;
/**
* Describes how the video should be scaled to fit in the container.
* Options are 'contain', 'cover', and 'fill'.
* @default 'contain'
*/
- contentFit: VideoContentFit | undefined;
+ contentFit?: VideoContentFit;
/**
* Determines whether fullscreen mode is allowed or not.
* @default true
*/
- allowsFullscreen: boolean | undefined;
+ allowsFullscreen?: boolean;
/**
* Determines whether the timecodes should be displayed or not.
* @default true
* @platform ios
*/
- showsTimecodes: boolean | undefined;
+ showsTimecodes?: boolean;
/**
* Determines whether the player allows the user to skip media content.
* @default false
* @platform android
* @platform ios
*/
- requiresLinearPlayback: boolean | undefined;
+ requiresLinearPlayback?: boolean;
/**
* Determines the position offset of the video inside the container.
* @default { dx: 0, dy: 0 }
* @platform ios
*/
- contentPosition: {
+ contentPosition?: {
dx?: number;
dy?: number;
- } | undefined;
+ };
/**
* A callback to call after the video player enters Picture in Picture (PiP) mode.
* @platform android
@@ -190,5 +203,47 @@ export type VideoSource = string | {
uri: string;
drm?: DRMOptions;
} | null;
+/**
+ * Handlers for events which can be emitted by the player.
+ */
+export type VideoPlayerEvents = {
+ /**
+ * Handler for an event emitted when the status of the player changes.
+ */
+ statusChange: (newStatus: PlayerStatus, oldStatus: PlayerStatus, error: PlayerError) => void;
+ /**
+ * Handler for an event emitted when the player starts or stops playback.
+ */
+ playingChange: (newIsPlaying: boolean, oldIsPlaying: boolean) => void;
+ /**
+ * Handler for an event emitted when the `playbackRate` property of the player changes.
+ */
+ playbackRateChange: (newPlaybackRate: number, oldPlaybackRate: number) => void;
+ /**
+ * Handler for an event emitted when the `volume` property of the player changes.
+ */
+ volumeChange: (newVolume: VolumeEvent, oldVolume: VolumeEvent) => void;
+ /**
+ * Handler for an event emitted when the player plays to the end of the current source.
+ */
+ playToEnd: () => void;
+ /**
+ * Handler for an event emitted when the current media source of the player changes.
+ */
+ sourceChange: (newSource: VideoSource, previousSource: VideoSource) => void;
+};
+/**
+ * Contains information about any errors that the player encountered during the playback
+ */
+type PlayerError = {
+ message: string;
+};
+/**
+ * Contains information about the current volume and whether the player is muted.
+ */
+type VolumeEvent = {
+ volume: number;
+ isMuted: boolean;
+};
export {};
//# sourceMappingURL=VideoView.types.d.ts.map
\ No newline at end of file
diff --git a/packages/expo-video/build/VideoView.types.d.ts.map b/packages/expo-video/build/VideoView.types.d.ts.map
index 65de877c800ad..f67846c876dc5 100644
--- a/packages/expo-video/build/VideoView.types.d.ts.map
+++ b/packages/expo-video/build/VideoView.types.d.ts.map
@@ -1 +1 @@
-{"version":3,"file":"VideoView.types.d.ts","sourceRoot":"","sources":["../src/VideoView.types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAC;AACtD,OAAO,EAAE,SAAS,EAAE,MAAM,cAAc,CAAC;AAEzC;;GAEG;AACH,MAAM,CAAC,OAAO,OAAO,WAAY,SAAQ,YAAY;IACnD;;;OAGG;IACH,OAAO,EAAE,OAAO,CAAC;IAEjB;;;OAGG;IACH,IAAI,EAAE,OAAO,CAAC;IAEd;;;OAGG;IACH,KAAK,EAAE,OAAO,CAAC;IAEf;;OAEG;IACH,WAAW,EAAE,MAAM,CAAC;IAEpB;;;;;OAKG;IACH,MAAM,EAAE,MAAM,CAAC;IAEf;;;;;;OAMG;IACH,cAAc,EAAE,OAAO,CAAC;IAExB;;;OAGG;IACH,YAAY,EAAE,MAAM,CAAC;IAErB;;;;;OAKG;IACH,uBAAuB,EAAE,OAAO,CAAC;IAEjC;;OAEG;IACH,IAAI,IAAI,IAAI;IAEZ;;OAEG;IACH,KAAK,IAAI,IAAI;IAEb;;OAEG;IACH,OAAO,CAAC,MAAM,EAAE,WAAW,GAAG,IAAI;IAElC;;OAEG;IACH,MAAM,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI;IAE7B;;OAEG;IACH,MAAM,IAAI,IAAI;CACf;AAED;;;;;GAKG;AACH,KAAK,eAAe,GAAG,SAAS,GAAG,OAAO,GAAG,MAAM,CAAC;AAEpD,MAAM,WAAW,cAAe,SAAQ,SAAS;IAC/C;;OAEG;IACH,MAAM,EAAE,WAAW,CAAC;IAEpB;;;OAGG;IACH,cAAc,EAAE,OAAO,GAAG,SAAS,CAAC;IAEpC;;;;OAIG;IACH,UAAU,EAAE,eAAe,GAAG,SAAS,CAAC;IAExC;;;OAGG;IACH,gBAAgB,EAAE,OAAO,GAAG,SAAS,CAAC;IAEtC;;;;OAIG;IACH,cAAc,EAAE,OAAO,GAAG,SAAS,CAAC;IAEpC;;;;;OAKG;IACH,sBAAsB,EAAE,OAAO,GAAG,SAAS,CAAC;IAE5C;;;;OAIG;IACH,eAAe,EAAE;QAAE,EAAE,CAAC,EAAE,MAAM,CAAC;QAAC,EAAE,CAAC,EAAE,MAAM,CAAA;KAAE,GAAG,SAAS,CAAC;IAE1D;;;;OAIG;IACH,uBAAuB,CAAC,EAAE,MAAM,IAAI,CAAC;IAErC;;;;OAIG;IACH,sBAAsB,CAAC,EAAE,MAAM,IAAI,CAAC;IAEpC;;;;OAIG;IACH,sBAAsB,CAAC,EAAE,OAAO,CAAC;IAEjC;;;;;;OAMG;IACH,mCAAmC,CAAC,EAAE,OAAO,CAAC;CAC/C;AAED;;KAEK;AACL,KAAK,OAAO,GAAG,UAAU,GAAG,UAAU,GAAG,WAAW,GAAG,UAAU,CAAC;AAElE;;GAEG;AACH,KAAK,UAAU,GAAG;IAChB;;OAEG;IACH,IAAI,EAAE,OAAO,CAAC;IAEd;;OAEG;IACH,aAAa,EAAE,MAAM,CAAC;IAEtB;;OAEG;IACH,OAAO,CAAC,EAAE;QAAE,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAAA;KAAE,CAAC;IAEpC;;;OAGG;IACH,QAAQ,CAAC,EAAE,OAAO,CAAC;IAEnB;;;OAGG;IACH,SAAS,CAAC,EAAE,MAAM,CAAC;IAEnB;;;OAGG;IACH,cAAc,CAAC,EAAE,MAAM,CAAC;CACzB,CAAC;AAEF,MAAM,MAAM,WAAW,GAAG,MAAM,GAAG;IAAE,GAAG,EAAE,MAAM,CAAC;IAAC,GAAG,CAAC,EAAE,UAAU,CAAA;CAAE,GAAG,IAAI,CAAC"}
\ No newline at end of file
+{"version":3,"file":"VideoView.types.d.ts","sourceRoot":"","sources":["../src/VideoView.types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAC;AACtD,OAAO,EAAE,SAAS,EAAE,MAAM,cAAc,CAAC;AAEzC;;GAEG;AACH,MAAM,CAAC,OAAO,OAAO,WAAY,SAAQ,YAAY,CAAC,iBAAiB,CAAC;IACtE;;;OAGG;IACH,OAAO,EAAE,OAAO,CAAC;IAEjB;;;OAGG;IACH,IAAI,EAAE,OAAO,CAAC;IAEd;;;OAGG;IACH,KAAK,EAAE,OAAO,CAAC;IAEf;;OAEG;IACH,WAAW,EAAE,MAAM,CAAC;IAEpB;;;;;OAKG;IACH,MAAM,EAAE,MAAM,CAAC;IAEf;;;;;;OAMG;IACH,cAAc,EAAE,OAAO,CAAC;IAExB;;;OAGG;IACH,YAAY,EAAE,MAAM,CAAC;IAErB;;;OAGG;IACH,MAAM,EAAE,YAAY,CAAC;IAErB;;;;;OAKG;IACH,uBAAuB,EAAE,OAAO,CAAC;IAEjC;;OAEG;IACH,IAAI,IAAI,IAAI;IAEZ;;OAEG;IACH,KAAK,IAAI,IAAI;IAEb;;OAEG;IACH,OAAO,CAAC,MAAM,EAAE,WAAW,GAAG,IAAI;IAElC;;OAEG;IACH,MAAM,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI;IAE7B;;OAEG;IACH,MAAM,IAAI,IAAI;CACf;AAED;;;;;GAKG;AACH,KAAK,eAAe,GAAG,SAAS,GAAG,OAAO,GAAG,MAAM,CAAC;AAEpD;;;;;;GAMG;AACH,MAAM,MAAM,YAAY,GAAG,MAAM,GAAG,SAAS,GAAG,aAAa,GAAG,OAAO,CAAC;AAExE,MAAM,WAAW,cAAe,SAAQ,SAAS;IAC/C;;OAEG;IACH,MAAM,EAAE,WAAW,CAAC;IAEpB;;;OAGG;IACH,cAAc,CAAC,EAAE,OAAO,CAAC;IAEzB;;;;OAIG;IACH,UAAU,CAAC,EAAE,eAAe,CAAC;IAE7B;;;OAGG;IACH,gBAAgB,CAAC,EAAE,OAAO,CAAC;IAE3B;;;;OAIG;IACH,cAAc,CAAC,EAAE,OAAO,CAAC;IAEzB;;;;;OAKG;IACH,sBAAsB,CAAC,EAAE,OAAO,CAAC;IAEjC;;;;OAIG;IACH,eAAe,CAAC,EAAE;QAAE,EAAE,CAAC,EAAE,MAAM,CAAC;QAAC,EAAE,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC;IAE/C;;;;OAIG;IACH,uBAAuB,CAAC,EAAE,MAAM,IAAI,CAAC;IAErC;;;;OAIG;IACH,sBAAsB,CAAC,EAAE,MAAM,IAAI,CAAC;IAEpC;;;;OAIG;IACH,sBAAsB,CAAC,EAAE,OAAO,CAAC;IAEjC;;;;;;OAMG;IACH,mCAAmC,CAAC,EAAE,OAAO,CAAC;CAC/C;AAED;;KAEK;AACL,KAAK,OAAO,GAAG,UAAU,GAAG,UAAU,GAAG,WAAW,GAAG,UAAU,CAAC;AAElE;;GAEG;AACH,KAAK,UAAU,GAAG;IAChB;;OAEG;IACH,IAAI,EAAE,OAAO,CAAC;IAEd;;OAEG;IACH,aAAa,EAAE,MAAM,CAAC;IAEtB;;OAEG;IACH,OAAO,CAAC,EAAE;QAAE,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAAA;KAAE,CAAC;IAEpC;;;OAGG;IACH,QAAQ,CAAC,EAAE,OAAO,CAAC;IAEnB;;;OAGG;IACH,SAAS,CAAC,EAAE,MAAM,CAAC;IAEnB;;;OAGG;IACH,cAAc,CAAC,EAAE,MAAM,CAAC;CACzB,CAAC;AAEF,MAAM,MAAM,WAAW,GAAG,MAAM,GAAG;IAAE,GAAG,EAAE,MAAM,CAAC;IAAC,GAAG,CAAC,EAAE,UAAU,CAAA;CAAE,GAAG,IAAI,CAAC;AAE5E;;GAEG;AACH,MAAM,MAAM,iBAAiB,GAAG;IAC9B;;OAEG;IACH,YAAY,EAAE,CAAC,SAAS,EAAE,YAAY,EAAE,SAAS,EAAE,YAAY,EAAE,KAAK,EAAE,WAAW,KAAK,IAAI,CAAC;IAC7F;;OAEG;IACH,aAAa,EAAE,CAAC,YAAY,EAAE,OAAO,EAAE,YAAY,EAAE,OAAO,KAAK,IAAI,CAAC;IACtE;;OAEG;IACH,kBAAkB,EAAE,CAAC,eAAe,EAAE,MAAM,EAAE,eAAe,EAAE,MAAM,KAAK,IAAI,CAAC;IAC/E;;OAEG;IACH,YAAY,EAAE,CAAC,SAAS,EAAE,WAAW,EAAE,SAAS,EAAE,WAAW,KAAK,IAAI,CAAC;IACvE;;OAEG;IACH,SAAS,EAAE,MAAM,IAAI,CAAC;IACtB;;OAEG;IACH,YAAY,EAAE,CAAC,SAAS,EAAE,WAAW,EAAE,cAAc,EAAE,WAAW,KAAK,IAAI,CAAC;CAC7E,CAAC;AAEF;;GAEG;AACH,KAAK,WAAW,GAAG;IACjB,OAAO,EAAE,MAAM,CAAC;CACjB,CAAC;AAEF;;GAEG;AACH,KAAK,WAAW,GAAG;IACjB,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,EAAE,OAAO,CAAC;CAClB,CAAC"}
\ No newline at end of file
diff --git a/packages/expo-video/build/VideoView.types.js.map b/packages/expo-video/build/VideoView.types.js.map
index e29713af0b684..24305ed664850 100644
--- a/packages/expo-video/build/VideoView.types.js.map
+++ b/packages/expo-video/build/VideoView.types.js.map
@@ -1 +1 @@
-{"version":3,"file":"VideoView.types.js","sourceRoot":"","sources":["../src/VideoView.types.ts"],"names":[],"mappings":"","sourcesContent":["import type { SharedObject } from 'expo-modules-core';\nimport { ViewProps } from 'react-native';\n\n/**\n * A class that represents an instance of the video player.\n */\nexport declare class VideoPlayer extends SharedObject {\n /**\n * Boolean value whether the player is currently playing.\n * > This property is get-only, use `play` and `pause` methods to control the playback.\n */\n playing: boolean;\n\n /**\n * Determines whether the player should automatically replay after reaching the end of the video.\n * @default false\n */\n loop: boolean;\n\n /**\n * Boolean value whether the player is currently muted.\n * @default false\n */\n muted: boolean;\n\n /**\n * Integer value representing the current position in seconds.\n */\n currentTime: number;\n\n /**\n * Float value between 0 and 1 representing the current volume.\n * Muting the player doesn't affect the volume. In other words, when the player is muted, the volume is the same as\n * when unmuted. Similarly, setting the volume doesn't unmute the player.\n * @default 1.0\n */\n volume: number;\n\n /**\n * Boolean value indicating if the player should correct audio pitch when the playback speed changes.\n * > On web, changing this property is not supported, the player will always correct the pitch.\n * @default true\n * @platform android\n * @platform ios\n */\n preservesPitch: boolean;\n\n /**\n * Float value between 0 and 16 indicating the current playback speed of the player.\n * @default 1.0\n */\n playbackRate: number;\n\n /**\n * Determines whether the player should continue playing after the app enters the background.\n * @default false\n * @platform ios\n * @platform android\n */\n staysActiveInBackground: boolean;\n\n /**\n * Resumes the player.\n */\n play(): void;\n\n /**\n * Pauses the player.\n */\n pause(): void;\n\n /**\n * Replaces the current source with a new one.\n */\n replace(source: VideoSource): void;\n\n /**\n * Seeks the playback by the given number of seconds.\n */\n seekBy(seconds: number): void;\n\n /**\n * Seeks the playback to the beginning.\n */\n replay(): void;\n}\n\n/**\n * Describes how a video should be scaled to fit in a container.\n * 'contain': The video maintains its aspect ratio and fits inside the container, with possible letterboxing/pillarboxing.\n * 'cover': The video maintains its aspect ratio and covers the entire container, potentially cropping some portions.\n * 'fill': The video stretches/squeezes to completely fill the container, potentially causing distortion.\n */\ntype VideoContentFit = 'contain' | 'cover' | 'fill';\n\nexport interface VideoViewProps extends ViewProps {\n /**\n * A player instance – use `useVideoPlayer()` to create one.\n */\n player: VideoPlayer;\n\n /**\n * Determines whether native controls should be displayed or not.\n * @default true\n */\n nativeControls: boolean | undefined;\n\n /**\n * Describes how the video should be scaled to fit in the container.\n * Options are 'contain', 'cover', and 'fill'.\n * @default 'contain'\n */\n contentFit: VideoContentFit | undefined;\n\n /**\n * Determines whether fullscreen mode is allowed or not.\n * @default true\n */\n allowsFullscreen: boolean | undefined;\n\n /**\n * Determines whether the timecodes should be displayed or not.\n * @default true\n * @platform ios\n */\n showsTimecodes: boolean | undefined;\n\n /**\n * Determines whether the player allows the user to skip media content.\n * @default false\n * @platform android\n * @platform ios\n */\n requiresLinearPlayback: boolean | undefined;\n\n /**\n * Determines the position offset of the video inside the container.\n * @default { dx: 0, dy: 0 }\n * @platform ios\n */\n contentPosition: { dx?: number; dy?: number } | undefined;\n\n /**\n * A callback to call after the video player enters Picture in Picture (PiP) mode.\n * @platform android\n * @platform ios 14+\n */\n onPictureInPictureStart?: () => void;\n\n /**\n * A callback to call after the video player exits Picture in Picture (PiP) mode.\n * @platform android\n * @platform ios 14+\n */\n onPictureInPictureStop?: () => void;\n\n /**\n * Determines whether the player allows Picture in Picture (PiP) mode.\n * @default false\n * @platform ios 14+\n */\n allowsPictureInPicture?: boolean;\n\n /**\n * Determines whether the player should start Picture in Picture (PiP) automatically when the app is in the background.\n * > **Note:** Only one player can be in Picture in Picture (PiP) mode at a time.\n * @default false\n * @platform android 12+\n * @platform ios 14.2+\n */\n startsPictureInPictureAutomatically?: boolean;\n}\n\n/**\n * Specifies which type of DRM to use. Android supports Widevine, PlayReady and ClearKey, iOS supports FairPlay.\n * */\ntype DRMType = 'clearkey' | 'fairplay' | 'playready' | 'widevine';\n\n/**\n * Specifies DRM options which will be used by the player while loading the video.\n */\ntype DRMOptions = {\n /**\n * Determines which type of DRM to use.\n */\n type: DRMType;\n\n /**\n * Determines the license server URL.\n */\n licenseServer: string;\n\n /**\n * Determines headers sent to the license server on license requests.\n */\n headers?: { [key: string]: string };\n\n /**\n * Specifies whether the DRM is a multi-key DRM.\n * @platform android\n */\n multiKey?: boolean;\n\n /**\n * Specifies the content ID of the stream.\n * @platform ios\n */\n contentId?: string;\n\n /**\n * Specifies the certificate URL for the FairPlay DRM.\n * @platform ios\n */\n certificateUrl?: string;\n};\n\nexport type VideoSource = string | { uri: string; drm?: DRMOptions } | null;\n"]}
\ No newline at end of file
+{"version":3,"file":"VideoView.types.js","sourceRoot":"","sources":["../src/VideoView.types.ts"],"names":[],"mappings":"","sourcesContent":["import type { SharedObject } from 'expo-modules-core';\nimport { ViewProps } from 'react-native';\n\n/**\n * A class that represents an instance of the video player.\n */\nexport declare class VideoPlayer extends SharedObject {\n /**\n * Boolean value whether the player is currently playing.\n * > This property is get-only, use `play` and `pause` methods to control the playback.\n */\n playing: boolean;\n\n /**\n * Determines whether the player should automatically replay after reaching the end of the video.\n * @default false\n */\n loop: boolean;\n\n /**\n * Boolean value whether the player is currently muted.\n * @default false\n */\n muted: boolean;\n\n /**\n * Integer value representing the current position in seconds.\n */\n currentTime: number;\n\n /**\n * Float value between 0 and 1 representing the current volume.\n * Muting the player doesn't affect the volume. In other words, when the player is muted, the volume is the same as\n * when unmuted. Similarly, setting the volume doesn't unmute the player.\n * @default 1.0\n */\n volume: number;\n\n /**\n * Boolean value indicating if the player should correct audio pitch when the playback speed changes.\n * > On web, changing this property is not supported, the player will always correct the pitch.\n * @default true\n * @platform android\n * @platform ios\n */\n preservesPitch: boolean;\n\n /**\n * Float value between 0 and 16 indicating the current playback speed of the player.\n * @default 1.0\n */\n playbackRate: number;\n\n /**\n * Indicates the current status of the player.\n * > This property is get-only\n */\n status: PlayerStatus;\n\n /**\n * Determines whether the player should continue playing after the app enters the background.\n * @default false\n * @platform ios\n * @platform android\n */\n staysActiveInBackground: boolean;\n\n /**\n * Resumes the player.\n */\n play(): void;\n\n /**\n * Pauses the player.\n */\n pause(): void;\n\n /**\n * Replaces the current source with a new one.\n */\n replace(source: VideoSource): void;\n\n /**\n * Seeks the playback by the given number of seconds.\n */\n seekBy(seconds: number): void;\n\n /**\n * Seeks the playback to the beginning.\n */\n replay(): void;\n}\n\n/**\n * Describes how a video should be scaled to fit in a container.\n * - `contain`: The video maintains its aspect ratio and fits inside the container, with possible letterboxing/pillarboxing.\n * - `cover`: The video maintains its aspect ratio and covers the entire container, potentially cropping some portions.\n * - `fill`: The video stretches/squeezes to completely fill the container, potentially causing distortion.\n */\ntype VideoContentFit = 'contain' | 'cover' | 'fill';\n\n/**\n * Describes the current status of the player.\n * - `idle`: The player is not playing or loading any videos.\n * - `loading`: The player is loading video data from the provided source\n * - `readyToPlay`: The player has loaded enough data to start playing or to continue playback.\n * - `error`: The player has encountered an error while loading or playing the video.\n */\nexport type PlayerStatus = 'idle' | 'loading' | 'readyToPlay' | 'error';\n\nexport interface VideoViewProps extends ViewProps {\n /**\n * A player instance – use `useVideoPlayer()` to create one.\n */\n player: VideoPlayer;\n\n /**\n * Determines whether native controls should be displayed or not.\n * @default true\n */\n nativeControls?: boolean;\n\n /**\n * Describes how the video should be scaled to fit in the container.\n * Options are 'contain', 'cover', and 'fill'.\n * @default 'contain'\n */\n contentFit?: VideoContentFit;\n\n /**\n * Determines whether fullscreen mode is allowed or not.\n * @default true\n */\n allowsFullscreen?: boolean;\n\n /**\n * Determines whether the timecodes should be displayed or not.\n * @default true\n * @platform ios\n */\n showsTimecodes?: boolean;\n\n /**\n * Determines whether the player allows the user to skip media content.\n * @default false\n * @platform android\n * @platform ios\n */\n requiresLinearPlayback?: boolean;\n\n /**\n * Determines the position offset of the video inside the container.\n * @default { dx: 0, dy: 0 }\n * @platform ios\n */\n contentPosition?: { dx?: number; dy?: number };\n\n /**\n * A callback to call after the video player enters Picture in Picture (PiP) mode.\n * @platform android\n * @platform ios 14+\n */\n onPictureInPictureStart?: () => void;\n\n /**\n * A callback to call after the video player exits Picture in Picture (PiP) mode.\n * @platform android\n * @platform ios 14+\n */\n onPictureInPictureStop?: () => void;\n\n /**\n * Determines whether the player allows Picture in Picture (PiP) mode.\n * @default false\n * @platform ios 14+\n */\n allowsPictureInPicture?: boolean;\n\n /**\n * Determines whether the player should start Picture in Picture (PiP) automatically when the app is in the background.\n * > **Note:** Only one player can be in Picture in Picture (PiP) mode at a time.\n * @default false\n * @platform android 12+\n * @platform ios 14.2+\n */\n startsPictureInPictureAutomatically?: boolean;\n}\n\n/**\n * Specifies which type of DRM to use. Android supports Widevine, PlayReady and ClearKey, iOS supports FairPlay.\n * */\ntype DRMType = 'clearkey' | 'fairplay' | 'playready' | 'widevine';\n\n/**\n * Specifies DRM options which will be used by the player while loading the video.\n */\ntype DRMOptions = {\n /**\n * Determines which type of DRM to use.\n */\n type: DRMType;\n\n /**\n * Determines the license server URL.\n */\n licenseServer: string;\n\n /**\n * Determines headers sent to the license server on license requests.\n */\n headers?: { [key: string]: string };\n\n /**\n * Specifies whether the DRM is a multi-key DRM.\n * @platform android\n */\n multiKey?: boolean;\n\n /**\n * Specifies the content ID of the stream.\n * @platform ios\n */\n contentId?: string;\n\n /**\n * Specifies the certificate URL for the FairPlay DRM.\n * @platform ios\n */\n certificateUrl?: string;\n};\n\nexport type VideoSource = string | { uri: string; drm?: DRMOptions } | null;\n\n/**\n * Handlers for events which can be emitted by the player.\n */\nexport type VideoPlayerEvents = {\n /**\n * Handler for an event emitted when the status of the player changes.\n */\n statusChange: (newStatus: PlayerStatus, oldStatus: PlayerStatus, error: PlayerError) => void;\n /**\n * Handler for an event emitted when the player starts or stops playback.\n */\n playingChange: (newIsPlaying: boolean, oldIsPlaying: boolean) => void;\n /**\n * Handler for an event emitted when the `playbackRate` property of the player changes.\n */\n playbackRateChange: (newPlaybackRate: number, oldPlaybackRate: number) => void;\n /**\n * Handler for an event emitted when the `volume` property of the player changes.\n */\n volumeChange: (newVolume: VolumeEvent, oldVolume: VolumeEvent) => void;\n /**\n * Handler for an event emitted when the player plays to the end of the current source.\n */\n playToEnd: () => void;\n /**\n * Handler for an event emitted when the current media source of the player changes.\n */\n sourceChange: (newSource: VideoSource, previousSource: VideoSource) => void;\n};\n\n/**\n * Contains information about any errors that the player encountered during the playback\n */\ntype PlayerError = {\n message: string;\n};\n\n/**\n * Contains information about the current volume and whether the player is muted.\n */\ntype VolumeEvent = {\n volume: number;\n isMuted: boolean;\n};\n"]}
\ No newline at end of file
diff --git a/packages/expo-video/build/VideoView.web.d.ts b/packages/expo-video/build/VideoView.web.d.ts
index c174858281a0f..2719cd29649c2 100644
--- a/packages/expo-video/build/VideoView.web.d.ts
+++ b/packages/expo-video/build/VideoView.web.d.ts
@@ -1,8 +1,8 @@
import React from 'react';
-import { VideoPlayer, VideoViewProps } from './VideoView.types';
+import { PlayerStatus, VideoPlayer, VideoSource, VideoViewProps } from './VideoView.types';
declare class VideoPlayerWeb implements VideoPlayer {
- constructor(source?: string | null);
- src: string | null;
+ constructor(source: VideoSource);
+ src: VideoSource;
_mountedVideos: Set;
_audioNodes: Set;
playing: boolean;
@@ -11,6 +11,7 @@ declare class VideoPlayerWeb implements VideoPlayer {
_loop: boolean;
_playbackRate: number;
_preservesPitch: boolean;
+ _status: PlayerStatus;
staysActiveInBackground: boolean;
set muted(value: boolean);
get muted(): boolean;
@@ -24,11 +25,12 @@ declare class VideoPlayerWeb implements VideoPlayer {
set currentTime(value: number);
get preservesPitch(): boolean;
set preservesPitch(value: boolean);
+ get status(): PlayerStatus;
mountVideoView(video: HTMLVideoElement): void;
unmountVideoView(video: HTMLVideoElement): void;
play(): void;
pause(): void;
- replace(source: string): void;
+ replace(source: VideoSource): void;
seekBy(seconds: number): void;
replay(): void;
_synchronizeWithFirstVideo(video: HTMLVideoElement): void;
@@ -39,7 +41,7 @@ declare class VideoPlayerWeb implements VideoPlayer {
removeAllListeners(eventName: never): void;
emit(eventName: EventName, ...args: Parameters[EventName]>): void;
}
-export declare function useVideoPlayer(source?: string | null): VideoPlayer;
+export declare function useVideoPlayer(source: VideoSource, setup?: (player: VideoPlayer) => void): VideoPlayer;
export declare const VideoView: React.ForwardRefExoticComponent<{
player?: VideoPlayerWeb | undefined;
} & VideoViewProps & React.RefAttributes>;
diff --git a/packages/expo-video/build/VideoView.web.d.ts.map b/packages/expo-video/build/VideoView.web.d.ts.map
index 691d17d8efa8d..2a28e25f2f7dc 100644
--- a/packages/expo-video/build/VideoView.web.d.ts.map
+++ b/packages/expo-video/build/VideoView.web.d.ts.map
@@ -1 +1 @@
-{"version":3,"file":"VideoView.web.d.ts","sourceRoot":"","sources":["../src/VideoView.web.tsx"],"names":[],"mappings":"AAAA,OAAO,KAA6D,MAAM,OAAO,CAAC;AAGlF,OAAO,EAAE,WAAW,EAAE,cAAc,EAAE,MAAM,mBAAmB,CAAC;AAiBhE,cAAM,cAAe,YAAW,WAAW;gBAC7B,MAAM,GAAE,MAAM,GAAG,IAAW;IAIxC,GAAG,EAAE,MAAM,GAAG,IAAI,CAAQ;IAC1B,cAAc,EAAE,GAAG,CAAC,gBAAgB,CAAC,CAAa;IAClD,WAAW,EAAE,GAAG,CAAC,2BAA2B,CAAC,CAAa;IAC1D,OAAO,EAAE,OAAO,CAAS;IACzB,MAAM,EAAE,OAAO,CAAS;IACxB,OAAO,EAAE,MAAM,CAAK;IACpB,KAAK,EAAE,OAAO,CAAS;IACvB,aAAa,EAAE,MAAM,CAAO;IAC5B,eAAe,EAAE,OAAO,CAAQ;IAChC,uBAAuB,EAAE,OAAO,CAAS;IAEzC,IAAI,KAAK,CAAC,KAAK,EAAE,OAAO,EAKvB;IACD,IAAI,KAAK,IAAI,OAAO,CAEnB;IAED,IAAI,YAAY,CAAC,KAAK,EAAE,MAAM,EAI7B;IAED,IAAI,YAAY,IAAI,MAAM,CAEzB;IAED,IAAI,MAAM,CAAC,KAAK,EAAE,MAAM,EAKvB;IAED,IAAI,MAAM,IAAI,MAAM,CAKnB;IAED,IAAI,IAAI,CAAC,KAAK,EAAE,OAAO,EAKtB;IAED,IAAI,IAAI,IAAI,OAAO,CAElB;IAED,IAAI,WAAW,IAAI,MAAM,CAGxB;IAED,IAAI,WAAW,CAAC,KAAK,EAAE,MAAM,EAI5B;IAED,IAAI,cAAc,IAAI,OAAO,CAE5B;IACD,IAAI,cAAc,CAAC,KAAK,EAAE,OAAO,EAKhC;IAED,cAAc,CAAC,KAAK,EAAE,gBAAgB;IAMtC,gBAAgB,CAAC,KAAK,EAAE,gBAAgB;IAgBxC,IAAI,IAAI,IAAI;IAMZ,KAAK,IAAI,IAAI;IAMb,OAAO,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI;IAS7B,MAAM,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI;IAK7B,MAAM,IAAI,IAAI;IAQd,0BAA0B,CAAC,KAAK,EAAE,gBAAgB,GAAG,IAAI;IASzD,aAAa,CAAC,KAAK,EAAE,gBAAgB,GAAG,IAAI;IAwD5C,OAAO,IAAI,IAAI;IAGf,WAAW,CAAC,SAAS,SAAS,KAAK,EACjC,SAAS,EAAE,SAAS,EACpB,QAAQ,EAAE,MAAM,CAAC,KAAK,EAAE,KAAK,CAAC,CAAC,SAAS,CAAC,GACxC,IAAI;IAGP,cAAc,CAAC,SAAS,SAAS,KAAK,EACpC,SAAS,EAAE,SAAS,EACpB,QAAQ,EAAE,MAAM,CAAC,KAAK,EAAE,KAAK,CAAC,CAAC,SAAS,CAAC,GACxC,IAAI;IAGP,kBAAkB,CAAC,SAAS,EAAE,KAAK,GAAG,IAAI;IAG1C,IAAI,CAAC,SAAS,SAAS,KAAK,EAC1B,SAAS,EAAE,SAAS,EACpB,GAAG,IAAI,EAAE,UAAU,CAAC,MAAM,CAAC,KAAK,EAAE,KAAK,CAAC,CAAC,SAAS,CAAC,CAAC,GACnD,IAAI;CAGR;AAQD,wBAAgB,cAAc,CAAC,MAAM,GAAE,MAAM,GAAG,IAAW,GAAG,WAAW,CAKxE;AAED,eAAO,MAAM,SAAS;;kDAqDpB,CAAC;AAEH,eAAe,SAAS,CAAC"}
\ No newline at end of file
+{"version":3,"file":"VideoView.web.d.ts","sourceRoot":"","sources":["../src/VideoView.web.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAsE,MAAM,OAAO,CAAC;AAG3F,OAAO,EAAE,YAAY,EAAE,WAAW,EAAE,WAAW,EAAE,cAAc,EAAE,MAAM,mBAAmB,CAAC;AAe3F,cAAM,cAAe,YAAW,WAAW;gBAC7B,MAAM,EAAE,WAAW;IAI/B,GAAG,EAAE,WAAW,CAAQ;IACxB,cAAc,EAAE,GAAG,CAAC,gBAAgB,CAAC,CAAa;IAClD,WAAW,EAAE,GAAG,CAAC,2BAA2B,CAAC,CAAa;IAC1D,OAAO,EAAE,OAAO,CAAS;IACzB,MAAM,EAAE,OAAO,CAAS;IACxB,OAAO,EAAE,MAAM,CAAK;IACpB,KAAK,EAAE,OAAO,CAAS;IACvB,aAAa,EAAE,MAAM,CAAO;IAC5B,eAAe,EAAE,OAAO,CAAQ;IAChC,OAAO,EAAE,YAAY,CAAU;IAC/B,uBAAuB,EAAE,OAAO,CAAS;IAEzC,IAAI,KAAK,CAAC,KAAK,EAAE,OAAO,EAKvB;IACD,IAAI,KAAK,IAAI,OAAO,CAEnB;IAED,IAAI,YAAY,CAAC,KAAK,EAAE,MAAM,EAI7B;IAED,IAAI,YAAY,IAAI,MAAM,CAEzB;IAED,IAAI,MAAM,CAAC,KAAK,EAAE,MAAM,EAKvB;IAED,IAAI,MAAM,IAAI,MAAM,CAKnB;IAED,IAAI,IAAI,CAAC,KAAK,EAAE,OAAO,EAKtB;IAED,IAAI,IAAI,IAAI,OAAO,CAElB;IAED,IAAI,WAAW,IAAI,MAAM,CAGxB;IAED,IAAI,WAAW,CAAC,KAAK,EAAE,MAAM,EAI5B;IAED,IAAI,cAAc,IAAI,OAAO,CAE5B;IACD,IAAI,cAAc,CAAC,KAAK,EAAE,OAAO,EAKhC;IAED,IAAI,MAAM,IAAI,YAAY,CAEzB;IAED,cAAc,CAAC,KAAK,EAAE,gBAAgB;IAMtC,gBAAgB,CAAC,KAAK,EAAE,gBAAgB;IAgBxC,IAAI,IAAI,IAAI;IAMZ,KAAK,IAAI,IAAI;IAMb,OAAO,CAAC,MAAM,EAAE,WAAW,GAAG,IAAI;IAclC,MAAM,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI;IAK7B,MAAM,IAAI,IAAI;IAQd,0BAA0B,CAAC,KAAK,EAAE,gBAAgB,GAAG,IAAI;IASzD,aAAa,CAAC,KAAK,EAAE,gBAAgB,GAAG,IAAI;IAoE5C,OAAO,IAAI,IAAI;IAGf,WAAW,CAAC,SAAS,SAAS,KAAK,EACjC,SAAS,EAAE,SAAS,EACpB,QAAQ,EAAE,MAAM,CAAC,KAAK,EAAE,KAAK,CAAC,CAAC,SAAS,CAAC,GACxC,IAAI;IAGP,cAAc,CAAC,SAAS,SAAS,KAAK,EACpC,SAAS,EAAE,SAAS,EACpB,QAAQ,EAAE,MAAM,CAAC,KAAK,EAAE,KAAK,CAAC,CAAC,SAAS,CAAC,GACxC,IAAI;IAGP,kBAAkB,CAAC,SAAS,EAAE,KAAK,GAAG,IAAI;IAG1C,IAAI,CAAC,SAAS,SAAS,KAAK,EAC1B,SAAS,EAAE,SAAS,EACpB,GAAG,IAAI,EAAE,UAAU,CAAC,MAAM,CAAC,KAAK,EAAE,KAAK,CAAC,CAAC,SAAS,CAAC,CAAC,GACnD,IAAI;CAGR;AAQD,wBAAgB,cAAc,CAC5B,MAAM,EAAE,WAAW,EACnB,KAAK,CAAC,EAAE,CAAC,MAAM,EAAE,WAAW,KAAK,IAAI,GACpC,WAAW,CAQb;AASD,eAAO,MAAM,SAAS;;kDAqDpB,CAAC;AAEH,eAAe,SAAS,CAAC"}
\ No newline at end of file
diff --git a/packages/expo-video/build/VideoView.web.js b/packages/expo-video/build/VideoView.web.js
index 58b9629dacaef..d0d5186250f13 100644
--- a/packages/expo-video/build/VideoView.web.js
+++ b/packages/expo-video/build/VideoView.web.js
@@ -1,4 +1,4 @@
-import React, { useEffect, useRef, forwardRef, useImperativeHandle } from 'react';
+import React, { useEffect, useRef, forwardRef, useImperativeHandle, useMemo } from 'react';
import { StyleSheet } from 'react-native';
/**
* This audio context is used to mute all but one video when multiple video views are playing from one player simultaneously.
@@ -14,7 +14,7 @@ else {
console.warn("Couldn't create AudioContext, this might affect the audio playback when using multiple video views with the same player.");
}
class VideoPlayerWeb {
- constructor(source = null) {
+ constructor(source) {
this.src = source;
}
src = null;
@@ -26,6 +26,7 @@ class VideoPlayerWeb {
_loop = false;
_playbackRate = 1.0;
_preservesPitch = true;
+ _status = 'idle';
staysActiveInBackground = false; // Not supported on web. Dummy to match the interface.
set muted(value) {
this._mountedVideos.forEach((video) => {
@@ -83,6 +84,9 @@ class VideoPlayerWeb {
});
this._preservesPitch = value;
}
+ get status() {
+ return this._status;
+ }
mountVideoView(video) {
this._mountedVideos.add(video);
this._synchronizeWithFirstVideo(video);
@@ -116,10 +120,16 @@ class VideoPlayerWeb {
}
replace(source) {
this._mountedVideos.forEach((video) => {
+ const uri = getSourceUri(source);
video.pause();
- video.setAttribute('src', source);
- video.load();
- video.play();
+ if (uri) {
+ video.setAttribute('src', uri);
+ video.load();
+ video.play();
+ }
+ else {
+ video.removeAttribute('src');
+ }
});
this.playing = true;
}
@@ -196,6 +206,15 @@ class VideoPlayerWeb {
mountedVideo.playbackRate = video.playbackRate;
});
};
+ video.onerror = () => {
+ this._status = 'error';
+ };
+ video.onloadeddata = () => {
+ this._status = 'readyToPlay';
+ };
+ video.onwaiting = () => {
+ this._status = 'loading';
+ };
}
release() {
console.warn('The `VideoPlayer.release` method is not supported on web');
@@ -218,11 +237,19 @@ function mapStyles(style) {
// Looking through react-native-web source code they also just pass styles directly without further conversions, so it's just a cast.
return flattenedStyles;
}
-export function useVideoPlayer(source = null) {
- return React.useMemo(() => {
- return new VideoPlayerWeb(source);
- // should this not include source?
- }, []);
+export function useVideoPlayer(source, setup) {
+ const parsedSource = typeof source === 'string' ? { uri: source } : source;
+ return useMemo(() => {
+ const player = new VideoPlayerWeb(parsedSource);
+ setup?.(player);
+ return player;
+ }, [JSON.stringify(source)]);
+}
+function getSourceUri(source) {
+ if (typeof source == 'string') {
+ return source;
+ }
+ return source?.uri ?? null;
}
export const VideoView = forwardRef((props, ref) => {
const videoRef = useRef(null);
@@ -264,7 +291,7 @@ export const VideoView = forwardRef((props, ref) => {
if (newRef) {
videoRef.current = newRef;
}
- }} src={props.player?.src ?? ''}/>);
+ }} src={getSourceUri(props.player?.src) ?? ''}/>);
});
export default VideoView;
//# sourceMappingURL=VideoView.web.js.map
\ No newline at end of file
diff --git a/packages/expo-video/build/VideoView.web.js.map b/packages/expo-video/build/VideoView.web.js.map
index eb2da08eeb2c1..abf4d23bfbbef 100644
--- a/packages/expo-video/build/VideoView.web.js.map
+++ b/packages/expo-video/build/VideoView.web.js.map
@@ -1 +1 @@
-{"version":3,"file":"VideoView.web.js","sourceRoot":"","sources":["../src/VideoView.web.tsx"],"names":[],"mappings":"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","sourcesContent":["import React, { useEffect, useRef, forwardRef, useImperativeHandle } from 'react';\nimport { StyleSheet } from 'react-native';\n\nimport { VideoPlayer, VideoViewProps } from './VideoView.types';\n\n/**\n * This audio context is used to mute all but one video when multiple video views are playing from one player simultaneously.\n * Using audio context nodes allows muting videos without displaying the mute icon in the video player.\n */\nconst audioContext = window && new window.AudioContext();\nconst zeroGainNode = audioContext && audioContext.createGain();\nif (audioContext && zeroGainNode) {\n zeroGainNode.gain.value = 0;\n zeroGainNode.connect(audioContext.destination);\n} else {\n console.warn(\n \"Couldn't create AudioContext, this might affect the audio playback when using multiple video views with the same player.\"\n );\n}\n\nclass VideoPlayerWeb implements VideoPlayer {\n constructor(source: string | null = null) {\n this.src = source;\n }\n\n src: string | null = null;\n _mountedVideos: Set = new Set();\n _audioNodes: Set = new Set();\n playing: boolean = false;\n _muted: boolean = false;\n _volume: number = 1;\n _loop: boolean = false;\n _playbackRate: number = 1.0;\n _preservesPitch: boolean = true;\n staysActiveInBackground: boolean = false; // Not supported on web. Dummy to match the interface.\n\n set muted(value: boolean) {\n this._mountedVideos.forEach((video) => {\n video.muted = value;\n });\n this._muted = value;\n }\n get muted(): boolean {\n return this._muted;\n }\n\n set playbackRate(value: number) {\n this._mountedVideos.forEach((video) => {\n video.playbackRate = value;\n });\n }\n\n get playbackRate(): number {\n return this._playbackRate;\n }\n\n set volume(value: number) {\n this._mountedVideos.forEach((video) => {\n video.volume = value;\n });\n this._volume = value;\n }\n\n get volume(): number {\n this._mountedVideos.forEach((video) => {\n this._volume = video.volume;\n });\n return this._volume;\n }\n\n set loop(value: boolean) {\n this._mountedVideos.forEach((video) => {\n video.loop = value;\n });\n this._loop = value;\n }\n\n get loop(): boolean {\n return this._loop;\n }\n\n get currentTime(): number {\n // All videos should be synchronized, so we return the position of the first video.\n return [...this._mountedVideos][0].currentTime;\n }\n\n set currentTime(value: number) {\n this._mountedVideos.forEach((video) => {\n video.currentTime = value;\n });\n }\n\n get preservesPitch(): boolean {\n return this._preservesPitch;\n }\n set preservesPitch(value: boolean) {\n this._mountedVideos.forEach((video) => {\n video.preservesPitch = value;\n });\n this._preservesPitch = value;\n }\n\n mountVideoView(video: HTMLVideoElement) {\n this._mountedVideos.add(video);\n this._synchronizeWithFirstVideo(video);\n this._addListeners(video);\n }\n\n unmountVideoView(video: HTMLVideoElement) {\n const mountedVideos = [...this._mountedVideos];\n const mediaElementSources = [...this._audioNodes];\n const videoIndex = mountedVideos.findIndex((value) => value === video);\n const videoPlayingAudio = mountedVideos[0];\n this._mountedVideos.delete(video);\n this._audioNodes.delete(mediaElementSources[videoIndex]);\n\n // If video playing audio has been removed, select a new video to be the audio player by disconnecting it from the mute node.\n if (videoPlayingAudio === video && this._audioNodes.size > 0 && audioContext) {\n const newMainAudioSource = [...this._audioNodes][0];\n newMainAudioSource.disconnect();\n newMainAudioSource.connect(audioContext.destination);\n }\n }\n\n play(): void {\n this._mountedVideos.forEach((video) => {\n video.play();\n });\n this.playing = true;\n }\n pause(): void {\n this._mountedVideos.forEach((video) => {\n video.pause();\n });\n this.playing = false;\n }\n replace(source: string): void {\n this._mountedVideos.forEach((video) => {\n video.pause();\n video.setAttribute('src', source);\n video.load();\n video.play();\n });\n this.playing = true;\n }\n seekBy(seconds: number): void {\n this._mountedVideos.forEach((video) => {\n video.currentTime += seconds;\n });\n }\n replay(): void {\n this._mountedVideos.forEach((video) => {\n video.currentTime = 0;\n video.play();\n });\n this.playing = true;\n }\n\n _synchronizeWithFirstVideo(video: HTMLVideoElement): void {\n const firstVideo = [...this._mountedVideos][0];\n if (!firstVideo) return;\n video.currentTime = firstVideo.currentTime;\n video.volume = firstVideo.volume;\n video.muted = firstVideo.muted;\n video.playbackRate = firstVideo.playbackRate;\n }\n\n _addListeners(video: HTMLVideoElement): void {\n video.onloadedmetadata = () => {\n if (!audioContext || !zeroGainNode) return;\n const source = audioContext.createMediaElementSource(video);\n this._audioNodes.add(source);\n\n // First mounted video should be connected to the audio context. All other videos have to be muted.\n if (this._audioNodes.size === 1) {\n source.connect(audioContext.destination);\n } else {\n source.connect(zeroGainNode);\n }\n };\n\n video.onplay = () => {\n this.playing = true;\n this._mountedVideos.forEach((mountedVideo) => {\n mountedVideo.play();\n });\n };\n\n video.onpause = () => {\n this.playing = false;\n this._mountedVideos.forEach((mountedVideo) => {\n mountedVideo.pause();\n });\n };\n\n video.onvolumechange = () => {\n this.volume = video.volume;\n this._muted = video.muted;\n };\n\n video.onseeking = () => {\n this._mountedVideos.forEach((mountedVideo) => {\n if (mountedVideo === video || mountedVideo.currentTime === video.currentTime) return;\n mountedVideo.currentTime = video.currentTime;\n });\n };\n\n video.onseeked = () => {\n this._mountedVideos.forEach((mountedVideo) => {\n if (mountedVideo === video || mountedVideo.currentTime === video.currentTime) return;\n mountedVideo.currentTime = video.currentTime;\n });\n };\n\n video.onratechange = () => {\n this._mountedVideos.forEach((mountedVideo) => {\n if (mountedVideo === video || mountedVideo.playbackRate === video.playbackRate) return;\n this._playbackRate = video.playbackRate;\n mountedVideo.playbackRate = video.playbackRate;\n });\n };\n }\n\n release(): void {\n console.warn('The `VideoPlayer.release` method is not supported on web');\n }\n addListener(\n eventName: EventName,\n listener: Record[EventName]\n ): void {\n console.warn('The `VideoPlayer.addListener` method is not yet supported on web');\n }\n removeListener(\n eventName: EventName,\n listener: Record[EventName]\n ): void {\n console.warn('The `VideoPlayer.removeListener` method is not yet supported on web');\n }\n removeAllListeners(eventName: never): void {\n console.warn('The `VideoPlayer.removeAllListeners` method is not yet supported on web');\n }\n emit(\n eventName: EventName,\n ...args: Parameters[EventName]>\n ): void {\n console.warn('The `VideoPlayer.emit` method is not yet supported on web');\n }\n}\n\nfunction mapStyles(style: VideoViewProps['style']): React.CSSProperties {\n const flattenedStyles = StyleSheet.flatten(style);\n // Looking through react-native-web source code they also just pass styles directly without further conversions, so it's just a cast.\n return flattenedStyles as React.CSSProperties;\n}\n\nexport function useVideoPlayer(source: string | null = null): VideoPlayer {\n return React.useMemo(() => {\n return new VideoPlayerWeb(source);\n // should this not include source?\n }, []);\n}\n\nexport const VideoView = forwardRef((props: { player?: VideoPlayerWeb } & VideoViewProps, ref) => {\n const videoRef = useRef(null);\n useImperativeHandle(ref, () => ({\n enterFullscreen: () => {\n if (!props.allowsFullscreen) {\n return;\n }\n videoRef.current?.requestFullscreen();\n },\n exitFullscreen: () => {\n document.exitFullscreen();\n },\n }));\n\n useEffect(() => {\n return () => {\n if (videoRef.current) {\n props.player?.unmountVideoView(videoRef.current);\n }\n };\n }, []);\n\n useEffect(() => {\n if (!props.player || !videoRef.current) {\n return;\n }\n props.player.mountVideoView(videoRef.current);\n return () => {\n if (videoRef.current) {\n props.player?.unmountVideoView(videoRef.current);\n }\n };\n }, [props.player]);\n\n return (\n