diff --git a/android/build.gradle b/android/build.gradle index a17296a177..3ad67d54c4 100644 --- a/android/build.gradle +++ b/android/build.gradle @@ -2,6 +2,7 @@ import com.android.Version apply plugin: 'com.android.library' apply plugin: 'kotlin-android' +apply plugin: 'kotlin-parcelize' buildscript { def kotlin_version = rootProject.ext.has('kotlinVersion') ? rootProject.ext.get('kotlinVersion') : project.properties['RNVideo_kotlinVersion'] diff --git a/android/src/fabric/java/com/brentvatne/exoplayer/ReactExoplayerViewManager.kt b/android/src/fabric/java/com/brentvatne/exoplayer/ReactExoplayerViewManager.kt new file mode 100644 index 0000000000..3f8fd5ea25 --- /dev/null +++ b/android/src/fabric/java/com/brentvatne/exoplayer/ReactExoplayerViewManager.kt @@ -0,0 +1,504 @@ +package com.brentvatne.exoplayer; + +import android.net.Uri +import android.text.TextUtils +import androidx.media3.common.util.Util +import androidx.media3.datasource.RawResourceDataSource +import androidx.media3.exoplayer.DefaultLoadControl +import com.brentvatne.common.toolbox.ReactBridgeUtils.safeGetInt +import com.brentvatne.common.toolbox.ReactBridgeUtils.safeGetString +import com.brentvatne.exoplayer.events.* +import com.brentvatne.common.api.ResizeMode +import com.brentvatne.common.api.SubtitleStyle +import com.facebook.react.bridge.Dynamic +import com.facebook.react.bridge.ReadableArray +import com.facebook.react.bridge.ReadableMap +import com.facebook.react.common.MapBuilder +import com.facebook.react.module.annotations.ReactModule +import com.facebook.react.uimanager.ThemedReactContext +import com.facebook.react.uimanager.ViewGroupManager +import com.facebook.react.uimanager.ViewManagerDelegate +import com.facebook.react.uimanager.annotations.ReactProp +import com.facebook.react.viewmanagers.RNCVideoManagerDelegate +import com.facebook.react.viewmanagers.RNCVideoManagerInterface +import java.util.* +import javax.annotation.Nullable + + +@ReactModule(name = "RNCVideo") +internal class ReactExoplayerViewManager() : ViewGroupManager(), RNCVideoManagerInterface { + private var config: ReactExoplayerConfig? = null + + private val mDelegate: ViewManagerDelegate = RNCVideoManagerDelegate(this) + + private val REACT_CLASS = "RNCVideo" + + private val PROP_SRC_URI = "uri" + private val PROP_START_POSITION = "startPosition" + private val PROP_SRC_CROP_START = "cropStart" + private val PROP_SRC_CROP_END = "cropEnd" + private val PROP_SRC_TYPE = "type" + private val PROP_SRC_HEADERS = "requestHeaders" + + private val PROP_DRM_TYPE = "drmType" + private val PROP_DRM_LICENSESERVER = "licenseServer" + private val PROP_DRM_HEADERS = "headers" + + private val PROP_SELECTED_TEXT_TRACK_TYPE = "selectedTextType" + private val PROP_SELECTED_TEXT_TRACK_VALUE = "value" + + private val PROP_SELECTED_AUDIO_TRACK_TYPE = "selectedAudioType" + private val PROP_SELECTED_AUDIO_TRACK_VALUE = "value" + + private val PROP_BUFFER_CONFIG_MIN_BUFFER_MS = "minBufferMs" + private val PROP_BUFFER_CONFIG_MAX_BUFFER_MS = "maxBufferMs" + private val PROP_BUFFER_CONFIG_BUFFER_FOR_PLAYBACK_MS = "bufferForPlaybackMs" + private val PROP_BUFFER_CONFIG_BUFFER_FOR_PLAYBACK_AFTER_REBUFFER_MS = "bufferForPlaybackAfterRebufferMs" + private val PROP_BUFFER_CONFIG_MAX_HEAP_ALLOCATION_PERCENT = "maxHeapAllocationPercent" + private val PROP_BUFFER_CONFIG_MIN_BACK_BUFFER_MEMORY_RESERVE_PERCENT = "minBackBufferMemoryReservePercent" + private val PROP_BUFFER_CONFIG_MIN_BUFFER_MEMORY_RESERVE_PERCENT = "minBufferMemoryReservePercent" + + private val PROP_SELECTED_VIDEO_TRACK_TYPE = "selectedVideoType" + private val PROP_SELECTED_VIDEO_TRACK_VALUE = "value" + + constructor(config: ReactExoplayerConfig) : this() { + this.config = config + } + + override fun getName(): String { + return this.REACT_CLASS + } + + override fun createViewInstance(p0: ThemedReactContext): ReactExoplayerView { + val view = ReactExoplayerView(p0, config) + return view + } + + override fun getExportedCustomDirectEventTypeConstants(): MutableMap? { + return mutableMapOf( + OnVideoLoadEvent.EVENT_NAME to MapBuilder.of("registrationName", "onVideoLoad"), + OnVideoProgressEvent.EVENT_NAME to MapBuilder.of("registrationName", "onVideoProgress"), + OnVideoLoadStartEvent.EVENT_NAME to MapBuilder.of("registrationName", "onVideoLoadStart"), + OnAudioTracksEvent.EVENT_NAME to MapBuilder.of("registrationName", "onAudioTracks"), + OnTextTracksEvent.EVENT_NAME to MapBuilder.of("registrationName", "onTextTracks"), + OnVideoTracksEvent.EVENT_NAME to MapBuilder.of("registrationName", "onVideoTracks"), + OnVideoBandwidthUpdateEvent.EVENT_NAME to MapBuilder.of("registrationName", "onBandwidthUpdate"), + OnVideoSeekEvent.EVENT_NAME to MapBuilder.of("registrationName", "onVideoSeek"), + OnReadyForDisplayEvent.EVENT_NAME to MapBuilder.of("registrationName", "onReadyForDisplay"), + OnVideoBufferEvent.EVENT_NAME to MapBuilder.of("registrationName", "onVideoBuffer"), + OnVideoPlaybackStateChangedEvent.EVENT_NAME to MapBuilder.of("registrationName", "onVideoPlaybackStateChanged"), + OnVideoIdleEvent.EVENT_NAME to MapBuilder.of("registrationName", "onVideoIdle"), + OnVideoEndEvent.EVENT_NAME to MapBuilder.of("registrationName", "onVideoEnd"), + OnVideoFullscreenPlayerWillPresentEvent.EVENT_NAME to MapBuilder.of("registrationName", "onVideoFullscreenPlayerWillPresent"), + OnVideoFullscreenPlayerDidPresentEvent.EVENT_NAME to MapBuilder.of("registrationName", "onVideoFullscreenPlayerDidPresent"), + OnVideoFullscreenPlayerWillDismissEvent.EVENT_NAME to MapBuilder.of("registrationName", "onVideoFullscreenPlayerWillDismiss"), + OnVideoFullscreenPlayerDidDismissEvent.EVENT_NAME to MapBuilder.of("registrationName", "onVideoFullscreenPlayerDidDismiss"), + OnVideoErrorEvent.EVENT_NAME to MapBuilder.of("registrationName", "onVideoError"), + OnPlaybackRateChangeEvent.EVENT_NAME to MapBuilder.of("registrationName", "onPlaybackRateChange"), + OnTimedMetadataEvent.EVENT_NAME to MapBuilder.of("registrationName", "onTimedMetadata"), + OnAudioFocusChangedEvent.EVENT_NAME to MapBuilder.of("registrationName", "onAudioFocusChanged"), + OnVideoAudioBecomingNoisyEvent.EVENT_NAME to MapBuilder.of("registrationName", "onAudioBecomingNoisy"), + OnReceiveAdEventEvent.EVENT_NAME to MapBuilder.of("registrationName", "onReceiveAdEvent"), + ) + } + + override fun receiveCommand(root: ReactExoplayerView, commandId: String?, args: ReadableArray?) { + mDelegate.receiveCommand(root, commandId, args) + } + + override fun onDropViewInstance(view: ReactExoplayerView) { + view.cleanUpResources(); + } + + @ReactProp(name = "src") + override fun setSrc(view: ReactExoplayerView?, src: ReadableMap?) { + if (view != null && src != null) { + val uriString = safeGetString(src, PROP_SRC_URI, null) + val startPositionMs = safeGetInt(src, PROP_START_POSITION, -1) + val cropStartMs = safeGetInt(src, PROP_SRC_CROP_START, -1) + val cropEndMs = safeGetInt(src, PROP_SRC_CROP_END, -1) + val extension = safeGetString(src, PROP_SRC_TYPE, null) + val headers: MutableMap = mutableMapOf() + val propSrcHeadersArray = if (src.hasKey(PROP_SRC_HEADERS)) src.getArray(PROP_SRC_HEADERS) else null + propSrcHeadersArray?.let { + if (it.size() > 0) { + for (i in 0 until it.size()) { + val current = it.getMap(i) + val key = if (current.hasKey("key")) current.getString("key") else null + val value = if (current.hasKey("value")) current.getString("value") else null + if (key != null && value != null) { + headers.put(key, value) + } + } + } + } + if (TextUtils.isEmpty(uriString)) { + view.clearSrc(); + return; + } + if (startsWithValidScheme(uriString ?: "")) { + val srcUri: Uri = Uri.parse(uriString) + if (srcUri != null) { + view.setSrc(srcUri, startPositionMs.toLong(), cropStartMs.toLong(), cropEndMs.toLong(), extension, headers) + } + } else { + val context = view.context + var identifier = context.getResources().getIdentifier( + uriString, + "drawable", + context.getPackageName() + ); + if (identifier == 0) { + identifier = context.getResources().getIdentifier( + uriString, + "raw", + context.getPackageName() + ); + } + if (identifier > 0) { + val srcUri = RawResourceDataSource.buildRawResourceUri(identifier); + if (srcUri != null) { + view.setRawSrc(srcUri, extension); + } + } else { + view.clearSrc(); + } + } + } + } + + @ReactProp(name = "drm") + override fun setDrm(view: ReactExoplayerView?, drm: ReadableMap?) { + if (drm != null && drm.hasKey(PROP_DRM_TYPE)) { + val drmType = if (drm.hasKey(PROP_DRM_TYPE)) drm.getString(PROP_DRM_TYPE) else null + val drmLicenseServer = if (drm.hasKey(PROP_DRM_LICENSESERVER)) drm.getString(PROP_DRM_LICENSESERVER) else null + val drmHeadersArray = if (drm.hasKey(PROP_DRM_HEADERS)) drm.getArray(PROP_DRM_HEADERS) else null + if (drmType != null && drmLicenseServer != null && Util.getDrmUuid(drmType) != null) { + val drmUUID = Util.getDrmUuid(drmType) + view?.setDrmType(drmUUID) + view?.setDrmLicenseUrl(drmLicenseServer) + if (drmHeadersArray != null) { + val drmKeyRequestPropertiesList: ArrayList = ArrayList() + for (i in 0 until drmHeadersArray.size()) { + val current = drmHeadersArray.getMap(i) + val key = if (current.hasKey("key")) current.getString("key") else null + val value = if (current.hasKey("value")) current.getString("value") else null + drmKeyRequestPropertiesList.add(key) + drmKeyRequestPropertiesList.add(value) + } + view?.setDrmLicenseHeader(drmKeyRequestPropertiesList.toArray(arrayOfNulls(0))) + } + view?.setUseTextureView(false) + } + } + } + + @ReactProp(name = "adTagUrl") + override fun setAdTagUrl(view: ReactExoplayerView?, uriString: String?) { + if (view != null && uriString != null) { + if (TextUtils.isEmpty(uriString)) { + return + } + val adTagUrl = Uri.parse(uriString) + view.setAdTagUrl(adTagUrl) + } + } + + override fun setAllowsExternalPlayback(view: ReactExoplayerView?, value: Boolean) { + // do nothing, ios only + } + + @ReactProp(name = "maxBitRate") + override fun setMaxBitRate(view: ReactExoplayerView?, maxBitRate: Float) { + view?.setMaxBitRateModifier(maxBitRate.toInt()); + } + + @ReactProp(name = "resizeMode") + override fun setResizeMode(view: ReactExoplayerView?, resizeModeOrdinalString: String?) { + if (view != null && resizeModeOrdinalString != null) { + view.setResizeModeModifier(convertToIntDef(resizeModeOrdinalString)); + } + } + + @ReactProp(name = "repeat", defaultBoolean = false) + override fun setRepeat(view: ReactExoplayerView?, repeat: Boolean) { + view?.setRepeatModifier(repeat) + } + + override fun setAutomaticallyWaitsToMinimizeStalling(view: ReactExoplayerView?, value: Boolean) { + // do nothing, ios only + } + + @ReactProp(name = "textTracks") + override fun setTextTracks(view: ReactExoplayerView?, textTracks: ReadableArray?) { + view?.setTextTracks(textTracks); + } + + @ReactProp(name = "selectedTextTrack") + override fun setSelectedTextTrack(view: ReactExoplayerView?, selectedTextTrack: ReadableMap?) { + var typeString: String? = null + var value: Dynamic? = null + if (selectedTextTrack != null) { + typeString = if (selectedTextTrack.hasKey(PROP_SELECTED_TEXT_TRACK_TYPE)) selectedTextTrack.getString(PROP_SELECTED_TEXT_TRACK_TYPE) else null + value = if (selectedTextTrack.hasKey(PROP_SELECTED_TEXT_TRACK_VALUE)) selectedTextTrack.getDynamic(PROP_SELECTED_TEXT_TRACK_VALUE) else null + } + view?.setSelectedTextTrack(typeString, value) + } + + @ReactProp(name = "selectedAudioTrack") + override fun setSelectedAudioTrack(view: ReactExoplayerView?, selectedAudioTrack: ReadableMap?) { + var typeString: String? = null + var value: Dynamic? = null + if (selectedAudioTrack != null) { + typeString = if (selectedAudioTrack.hasKey(PROP_SELECTED_AUDIO_TRACK_TYPE)) selectedAudioTrack.getString(PROP_SELECTED_AUDIO_TRACK_TYPE) else null + value = if (selectedAudioTrack.hasKey(PROP_SELECTED_AUDIO_TRACK_VALUE)) selectedAudioTrack.getDynamic(PROP_SELECTED_AUDIO_TRACK_VALUE) else null + } + view?.setSelectedAudioTrack(typeString, value) + } + + @ReactProp(name = "paused", defaultBoolean = false) + override fun setPaused(view: ReactExoplayerView?, paused: Boolean) { + view?.setPausedModifier(paused) + } + + @ReactProp(name = "muted", defaultBoolean = false) + override fun setMuted(view: ReactExoplayerView?, muted: Boolean) { + view?.setMutedModifier(muted); + } + + @ReactProp(name = "controls", defaultBoolean = false) + override fun setControls(view: ReactExoplayerView?, controls: Boolean) { + view?.setControls(controls) + } + + override fun setFilter(view: ReactExoplayerView?, value: String?) { + // do nothing, ios only + } + + override fun setFilterEnabled(view: ReactExoplayerView?, value: Boolean) { + // do nothing, ios only + } + + @ReactProp(name = "volume", defaultFloat = 1.0f) + override fun setVolume(view: ReactExoplayerView?, volume: Float) { + view?.setVolumeModifier(volume); + } + + @ReactProp(name = "playInBackground", defaultBoolean = false) + override fun setPlayInBackground(view: ReactExoplayerView?, playInBackground: Boolean) { + view?.setPlayInBackground(playInBackground); + } + + @ReactProp(name = "preventsDisplaySleepDuringVideoPlayback", defaultBoolean = false) + override fun setPreventsDisplaySleepDuringVideoPlayback(view: ReactExoplayerView?, preventsSleep: Boolean) { + view?.setPreventsDisplaySleepDuringVideoPlayback(preventsSleep); + } + + override fun setPreferredForwardBufferDuration(view: ReactExoplayerView?, value: Float) { + // do nothing, ios only + } + + override fun setPlayWhenInactive(view: ReactExoplayerView?, value: Boolean) { + // do nothing, ios only + } + + override fun setPictureInPicture(view: ReactExoplayerView?, value: Boolean) { + // do nothing, ios only + } + + override fun setIgnoreSilentSwitch(view: ReactExoplayerView?, value: String?) { + // do nothing, ios only + } + + override fun setMixWithOthers(view: ReactExoplayerView?, value: String?) { + // do nothing, ios only + } + + @ReactProp(name = "rate") + override fun setRate(view: ReactExoplayerView?, rate: Float) { + view?.setRateModifier(rate) + } + + @ReactProp(name = "fullscreen", defaultBoolean = false) + override fun setFullscreen(view: ReactExoplayerView?, value: Boolean) { + // do nothing, ios only + } + + override fun setFullscreenAutorotate(view: ReactExoplayerView?, value: Boolean) { + // do nothing, ios only + } + + override fun setFullscreenOrientation(view: ReactExoplayerView?, value: String?) { + // do nothing, ios only + } + + @ReactProp(name = "progressUpdateInterval", defaultFloat = 250.0f) + override fun setProgressUpdateInterval(view: ReactExoplayerView?, progressUpdateInterval: Float) { + view?.setProgressUpdateInterval(progressUpdateInterval); + } + + override fun setRestoreUserInterfaceForPIPStopCompletionHandler(view: ReactExoplayerView?, value: Boolean) { + // do nothing, ios only + } + + override fun setLocalSourceEncryptionKeyScheme(view: ReactExoplayerView?, value: String?) { + // do nothing, ios only + } + + override fun save(view: ReactExoplayerView?) { + // do nothing, ios only + } + + override fun seek(view: ReactExoplayerView?, time: Float, tolerance: Float) { + // todo: what is tolerance for? + val to = Math.round(time * 1000f) + view?.seekTo(to.toLong()); + } + + override fun setLicenseResult(view: ReactExoplayerView?, result: String?) { + // do nothing, ios only + } + + override fun setLicenseResultError(view: ReactExoplayerView?, error: String?) { + // do nothing, ios only + } + + private fun startsWithValidScheme(uriString: String): Boolean { + val lowerCaseUri = uriString.lowercase(Locale.getDefault()) + return (lowerCaseUri.startsWith("http://") + || lowerCaseUri.startsWith("https://") + || lowerCaseUri.startsWith("content://") + || lowerCaseUri.startsWith("file://") + || lowerCaseUri.startsWith("asset://")) + } + + /** + * toStringMap converts a [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' + */ + fun toStringMap(@Nullable readableMap: ReadableMap?): Map? { + if (readableMap == null) return null + val iterator = readableMap.keySetIterator() + if (!iterator.hasNextKey()) return null + val result: MutableMap = HashMap() + while (iterator.hasNextKey()) { + val key = iterator.nextKey() + result[key] = readableMap.getString(key) + } + return result + } + + @ResizeMode.Mode + private fun convertToIntDef(resizeModeOrdinalString: String): Int { + if (!TextUtils.isEmpty(resizeModeOrdinalString)) { + if (resizeModeOrdinalString == "none") { + return ResizeMode.toResizeMode(ResizeMode.RESIZE_MODE_FIT) + } else if (resizeModeOrdinalString == "contain") { + return ResizeMode.toResizeMode(ResizeMode.RESIZE_MODE_FIT) + } else if (resizeModeOrdinalString == "cover") { + return ResizeMode.toResizeMode(ResizeMode.RESIZE_MODE_CENTER_CROP) + } else if (resizeModeOrdinalString == "stretch") { + return ResizeMode.toResizeMode(ResizeMode.RESIZE_MODE_FILL) + } + } + return ResizeMode.RESIZE_MODE_FIT + } + + @ReactProp(name = "backBufferDurationMs", defaultInt = 0) + override fun setBackBufferDurationMs(view: ReactExoplayerView?, value: Int) { + view?.setBackBufferDurationMs(value) + } + + @ReactProp(name = "bufferConfig") + override fun setBufferConfig(view: ReactExoplayerView?, bufferConfig: ReadableMap?) { + var minBufferMs = DefaultLoadControl.DEFAULT_MIN_BUFFER_MS + var maxBufferMs = DefaultLoadControl.DEFAULT_MAX_BUFFER_MS + var bufferForPlaybackMs = DefaultLoadControl.DEFAULT_BUFFER_FOR_PLAYBACK_MS + var bufferForPlaybackAfterRebufferMs = DefaultLoadControl.DEFAULT_BUFFER_FOR_PLAYBACK_AFTER_REBUFFER_MS + var maxHeapAllocationPercent = ReactExoplayerView.DEFAULT_MAX_HEAP_ALLOCATION_PERCENT + var minBackBufferMemoryReservePercent = ReactExoplayerView.DEFAULT_MIN_BACK_BUFFER_MEMORY_RESERVE + var minBufferMemoryReservePercent = ReactExoplayerView.DEFAULT_MIN_BUFFER_MEMORY_RESERVE + + if (bufferConfig != null) { + minBufferMs = if (bufferConfig.hasKey(PROP_BUFFER_CONFIG_MIN_BUFFER_MS)) bufferConfig.getInt(PROP_BUFFER_CONFIG_MIN_BUFFER_MS) else minBufferMs + maxBufferMs = if (bufferConfig.hasKey(PROP_BUFFER_CONFIG_MAX_BUFFER_MS)) bufferConfig.getInt(PROP_BUFFER_CONFIG_MAX_BUFFER_MS) else maxBufferMs + bufferForPlaybackMs = if (bufferConfig.hasKey(PROP_BUFFER_CONFIG_BUFFER_FOR_PLAYBACK_MS)) bufferConfig.getInt(PROP_BUFFER_CONFIG_BUFFER_FOR_PLAYBACK_MS) else bufferForPlaybackMs + bufferForPlaybackAfterRebufferMs = if (bufferConfig.hasKey(PROP_BUFFER_CONFIG_BUFFER_FOR_PLAYBACK_AFTER_REBUFFER_MS)) bufferConfig.getInt(PROP_BUFFER_CONFIG_BUFFER_FOR_PLAYBACK_AFTER_REBUFFER_MS) else bufferForPlaybackAfterRebufferMs + maxHeapAllocationPercent = if (bufferConfig.hasKey(PROP_BUFFER_CONFIG_MAX_HEAP_ALLOCATION_PERCENT)) bufferConfig.getDouble(PROP_BUFFER_CONFIG_MAX_HEAP_ALLOCATION_PERCENT) else maxHeapAllocationPercent + minBackBufferMemoryReservePercent = if (bufferConfig.hasKey(PROP_BUFFER_CONFIG_MIN_BACK_BUFFER_MEMORY_RESERVE_PERCENT)) bufferConfig.getDouble(PROP_BUFFER_CONFIG_MIN_BACK_BUFFER_MEMORY_RESERVE_PERCENT) else minBackBufferMemoryReservePercent + minBufferMemoryReservePercent = if (bufferConfig.hasKey(PROP_BUFFER_CONFIG_MIN_BUFFER_MEMORY_RESERVE_PERCENT)) bufferConfig.getDouble(PROP_BUFFER_CONFIG_MIN_BUFFER_MEMORY_RESERVE_PERCENT) else minBufferMemoryReservePercent + view?.setBufferConfig(minBufferMs, maxBufferMs, bufferForPlaybackMs, bufferForPlaybackAfterRebufferMs, maxHeapAllocationPercent, minBackBufferMemoryReservePercent, minBufferMemoryReservePercent) + } + } + + @ReactProp(name = "contentStartTime", defaultInt = -1) + override fun setContentStartTime(view: ReactExoplayerView?, value: Int) { + view?.setContentStartTime(value) + } + + override fun setCurrentPlaybackTime(view: ReactExoplayerView?, value: Double) { + // do nothing + } + + @ReactProp(name = "disableDisconnectError", defaultBoolean = false) + override fun setDisableDisconnectError(view: ReactExoplayerView?, value: Boolean) { + view?.setDisableDisconnectError(value) + } + + @ReactProp(name = "focusable", defaultBoolean = true) + override fun setFocusable(view: ReactExoplayerView?, value: Boolean) { + view?.setFocusable(value) + } + + @ReactProp(name = "hideShutterView", defaultBoolean = false) + override fun setHideShutterView(view: ReactExoplayerView?, value: Boolean) { + view?.setHideShutterView(value) + } + + @ReactProp(name = "minLoadRetryCount") + override fun setMinLoadRetryCount(view: ReactExoplayerView?, value: Int) { + view?.setMinLoadRetryCountModifier(value) + } + + @ReactProp(name = "reportBandwidth", defaultBoolean = false) + override fun setReportBandwidth(view: ReactExoplayerView?, value: Boolean) { + view?.setReportBandwidth(value) + } + + @ReactProp(name = "selectedVideoTrack") + override fun setSelectedVideoTrack(view: ReactExoplayerView?, selectedVideoTrack: ReadableMap?) { + var typeString: String? = null + var value: Dynamic? = null + if (selectedVideoTrack != null) { + typeString = if (selectedVideoTrack.hasKey(PROP_SELECTED_VIDEO_TRACK_TYPE)) selectedVideoTrack.getString(PROP_SELECTED_VIDEO_TRACK_TYPE) else null + value = if (selectedVideoTrack.hasKey(PROP_SELECTED_VIDEO_TRACK_VALUE)) selectedVideoTrack.getDynamic(PROP_SELECTED_VIDEO_TRACK_VALUE) else null + } + view?.setSelectedVideoTrack(typeString, value) + } + + @ReactProp(name = "subtitleStyle") + override fun setSubtitleStyle(view: ReactExoplayerView?, subtitleStyle: ReadableMap?) { + view?.setSubtitleStyle(SubtitleStyle.parse(subtitleStyle)); + } + + override fun setTrackId(view: ReactExoplayerView?, value: String?) { + // do nothing + } + + @ReactProp(name = "useTextureView", defaultBoolean = true) + override fun setUseTextureView(view: ReactExoplayerView?, value: Boolean) { + view?.setUseTextureView(value) + } + + @ReactProp(name = "useSecureView", defaultBoolean = true) + override fun setUseSecureView(view: ReactExoplayerView?, value: Boolean) { + view?.useSecureView(value) + } + + override fun setDebug(view: ReactExoplayerView?, value: ReadableMap?) { + TODO("Not yet implemented") + } +} diff --git a/android/src/fabric/java/com/brentvatne/exoplayer/VideoEventEmitter.java b/android/src/fabric/java/com/brentvatne/exoplayer/VideoEventEmitter.java new file mode 100644 index 0000000000..b7c6e2da64 --- /dev/null +++ b/android/src/fabric/java/com/brentvatne/exoplayer/VideoEventEmitter.java @@ -0,0 +1,363 @@ +package com.brentvatne.exoplayer; + +import androidx.annotation.StringDef; +import androidx.media3.common.Metadata; + +import android.view.View; + +import com.brentvatne.common.api.TimedMetadata; +import com.brentvatne.common.api.Track; +import com.brentvatne.common.api.VideoTrack; +import com.brentvatne.exoplayer.events.OnAudioFocusChangedEvent; +import com.brentvatne.exoplayer.events.OnAudioTracksEvent; +import com.brentvatne.exoplayer.events.OnPlaybackRateChangeEvent; +import com.brentvatne.exoplayer.events.OnReadyForDisplayEvent; +import com.brentvatne.exoplayer.events.OnReceiveAdEventEvent; +import com.brentvatne.exoplayer.events.OnTextTracksEvent; +import com.brentvatne.exoplayer.events.OnTimedMetadataEvent; +import com.brentvatne.exoplayer.events.OnVideoAudioBecomingNoisyEvent; +import com.brentvatne.exoplayer.events.OnVideoBandwidthUpdateEvent; +import com.brentvatne.exoplayer.events.OnVideoBufferEvent; +import com.brentvatne.exoplayer.events.OnVideoEndEvent; +import com.brentvatne.exoplayer.events.OnVideoErrorEvent; +import com.brentvatne.exoplayer.events.OnVideoFullscreenPlayerDidDismissEvent; +import com.brentvatne.exoplayer.events.OnVideoFullscreenPlayerDidPresentEvent; +import com.brentvatne.exoplayer.events.OnVideoFullscreenPlayerWillDismissEvent; +import com.brentvatne.exoplayer.events.OnVideoFullscreenPlayerWillPresentEvent; +import com.brentvatne.exoplayer.events.OnVideoIdleEvent; +import com.brentvatne.exoplayer.events.OnVideoLoadEvent; +import com.brentvatne.exoplayer.events.OnVideoLoadStartEvent; +import com.brentvatne.exoplayer.events.OnVideoPlaybackStateChangedEvent; +import com.brentvatne.exoplayer.events.OnVideoProgressEvent; +import com.brentvatne.exoplayer.events.OnVideoSeekEvent; +import com.brentvatne.exoplayer.events.OnVideoTracksEvent; +import com.facebook.react.bridge.Arguments; +import com.facebook.react.bridge.ReactContext; +import com.facebook.react.bridge.WritableArray; +import com.facebook.react.bridge.WritableMap; +import com.facebook.react.uimanager.UIManagerHelper; +import com.facebook.react.uimanager.events.EventDispatcher; +import com.facebook.react.uimanager.events.RCTEventEmitter; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.util.ArrayList; + +class VideoEventEmitter { + + private final RCTEventEmitter eventEmitter; + + private int viewId = View.NO_ID; + private ReactContext context = null; + + VideoEventEmitter(ReactContext reactContext) { + this.eventEmitter = reactContext.getJSModule(RCTEventEmitter.class); + this.context = reactContext; + } + + private static final String EVENT_LOAD_START = "onVideoLoadStart"; + private static final String EVENT_LOAD = "onVideoLoad"; + private static final String EVENT_ERROR = "onVideoError"; + private static final String EVENT_PROGRESS = "onVideoProgress"; + private static final String EVENT_BANDWIDTH = "onVideoBandwidthUpdate"; + private static final String EVENT_SEEK = "onVideoSeek"; + private static final String EVENT_END = "onVideoEnd"; + private static final String EVENT_FULLSCREEN_WILL_PRESENT = "onVideoFullscreenPlayerWillPresent"; + private static final String EVENT_FULLSCREEN_DID_PRESENT = "onVideoFullscreenPlayerDidPresent"; + private static final String EVENT_FULLSCREEN_WILL_DISMISS = "onVideoFullscreenPlayerWillDismiss"; + private static final String EVENT_FULLSCREEN_DID_DISMISS = "onVideoFullscreenPlayerDidDismiss"; + + private static final String EVENT_STALLED = "onPlaybackStalled"; + private static final String EVENT_RESUME = "onPlaybackResume"; + private static final String EVENT_READY = "onReadyForDisplay"; + private static final String EVENT_BUFFER = "onVideoBuffer"; + private static final String EVENT_PLAYBACK_STATE_CHANGED = "onVideoPlaybackStateChanged"; + private static final String EVENT_IDLE = "onVideoIdle"; + private static final String EVENT_TIMED_METADATA = "onTimedMetadata"; + private static final String EVENT_AUDIO_BECOMING_NOISY = "onVideoAudioBecomingNoisy"; + private static final String EVENT_AUDIO_FOCUS_CHANGE = "onAudioFocusChanged"; + private static final String EVENT_PLAYBACK_RATE_CHANGE = "onPlaybackRateChange"; + private static final String EVENT_AUDIO_TRACKS = "onAudioTracks"; + private static final String EVENT_TEXT_TRACKS = "onTextTracks"; + private static final String EVENT_VIDEO_TRACKS = "onVideoTracks"; + private static final String EVENT_ON_RECEIVE_AD_EVENT = "onReceiveAdEvent"; + + static final String[] Events = { + EVENT_LOAD_START, + EVENT_LOAD, + EVENT_ERROR, + EVENT_PROGRESS, + EVENT_SEEK, + EVENT_END, + EVENT_FULLSCREEN_WILL_PRESENT, + EVENT_FULLSCREEN_DID_PRESENT, + EVENT_FULLSCREEN_WILL_DISMISS, + EVENT_FULLSCREEN_DID_DISMISS, + EVENT_STALLED, + EVENT_RESUME, + EVENT_READY, + EVENT_BUFFER, + EVENT_PLAYBACK_STATE_CHANGED, + EVENT_IDLE, + EVENT_TIMED_METADATA, + EVENT_AUDIO_BECOMING_NOISY, + EVENT_AUDIO_FOCUS_CHANGE, + EVENT_PLAYBACK_RATE_CHANGE, + EVENT_AUDIO_TRACKS, + EVENT_TEXT_TRACKS, + EVENT_VIDEO_TRACKS, + EVENT_BANDWIDTH, + EVENT_ON_RECEIVE_AD_EVENT + }; + + @Retention(RetentionPolicy.SOURCE) + @StringDef({ + EVENT_LOAD_START, + EVENT_LOAD, + EVENT_ERROR, + EVENT_PROGRESS, + EVENT_SEEK, + EVENT_END, + EVENT_FULLSCREEN_WILL_PRESENT, + EVENT_FULLSCREEN_DID_PRESENT, + EVENT_FULLSCREEN_WILL_DISMISS, + EVENT_FULLSCREEN_DID_DISMISS, + EVENT_STALLED, + EVENT_RESUME, + EVENT_READY, + EVENT_BUFFER, + EVENT_PLAYBACK_STATE_CHANGED, + EVENT_IDLE, + EVENT_TIMED_METADATA, + EVENT_AUDIO_BECOMING_NOISY, + EVENT_AUDIO_FOCUS_CHANGE, + EVENT_PLAYBACK_RATE_CHANGE, + EVENT_AUDIO_TRACKS, + EVENT_TEXT_TRACKS, + EVENT_VIDEO_TRACKS, + EVENT_BANDWIDTH, + EVENT_ON_RECEIVE_AD_EVENT + }) + @interface VideoEvents { + } + + void setViewId(int viewId) { + this.viewId = viewId; + } + + void loadStart() { + EventDispatcher eventDispatcher = + UIManagerHelper.getEventDispatcherForReactTag(this.context, viewId); + eventDispatcher.dispatchEvent(new OnVideoLoadStartEvent(viewId)); + } + + WritableArray audioTracksToArray(ArrayList audioTracks) { + WritableArray waAudioTracks = Arguments.createArray(); + if( audioTracks != null ){ + for (int i = 0; i < audioTracks.size(); ++i) { + Track format = audioTracks.get(i); + WritableMap audioTrack = Arguments.createMap(); + audioTrack.putInt("index", i); + audioTrack.putString("title", format.getTitle() != null ? format.getTitle() : ""); + audioTrack.putString("type", format.getMimeType() != null ? format.getMimeType() : ""); + audioTrack.putString("language", format.getLanguage() != null ? format.getLanguage() : ""); + audioTrack.putInt("bitrate", format.getBitrate()); + audioTrack.putBoolean("selected", format.isSelected()); + waAudioTracks.pushMap(audioTrack); + } + } + return waAudioTracks; + } + + WritableArray videoTracksToArray(ArrayList videoTracks) { + WritableArray waVideoTracks = Arguments.createArray(); + if( videoTracks != null ){ + for (int i = 0; i < videoTracks.size(); ++i) { + VideoTrack vTrack = videoTracks.get(i); + WritableMap videoTrack = Arguments.createMap(); + videoTrack.putInt("width", vTrack.getWidth()); + videoTrack.putInt("height",vTrack.getHeight()); + videoTrack.putInt("bitrate", vTrack.getBitrate()); + videoTrack.putString("codecs", vTrack.getCodecs()); + videoTrack.putInt("trackId",vTrack.getId()); + videoTrack.putBoolean("selected", vTrack.isSelected()); + waVideoTracks.pushMap(videoTrack); + } + } + return waVideoTracks; + } + + WritableArray textTracksToArray(ArrayList textTracks) { + WritableArray waTextTracks = Arguments.createArray(); + if (textTracks != null) { + for (int i = 0; i < textTracks.size(); ++i) { + Track format = textTracks.get(i); + WritableMap textTrack = Arguments.createMap(); + textTrack.putInt("index", i); + textTrack.putString("title", format.getTitle() != null ? format.getTitle() : ""); + textTrack.putString("type", format.getMimeType() != null ? format.getMimeType() : ""); + textTrack.putString("language", format.getLanguage() != null ? format.getLanguage() : ""); + textTrack.putBoolean("selected", format.isSelected()); + waTextTracks.pushMap(textTrack); + } + } + return waTextTracks; + } + + public void load(double duration, double currentPosition, int videoWidth, int videoHeight, + ArrayList audioTracks, ArrayList textTracks, ArrayList videoTracks, String trackId){ + WritableArray waAudioTracks = audioTracksToArray(audioTracks); + WritableArray waVideoTracks = videoTracksToArray(videoTracks); + WritableArray waTextTracks = textTracksToArray(textTracks); + + load(duration, currentPosition, videoWidth, videoHeight, waAudioTracks, waTextTracks, waVideoTracks, trackId); + } + + + private void load(double duration, double currentPosition, int videoWidth, int videoHeight, + WritableArray audioTracks, WritableArray textTracks, WritableArray videoTracks, String trackId) { + EventDispatcher eventDispatcher = + UIManagerHelper.getEventDispatcherForReactTag(this.context, viewId); + eventDispatcher.dispatchEvent(new OnVideoLoadEvent(viewId, duration, currentPosition, videoWidth, videoHeight, audioTracks, textTracks, videoTracks, trackId)); + } + + WritableMap arrayToObject(String field, WritableArray array) { + WritableMap event = Arguments.createMap(); + event.putArray(field, array); + return event; + } + + public void audioTracks(ArrayList audioTracks){ + EventDispatcher eventDispatcher = + UIManagerHelper.getEventDispatcherForReactTag(this.context, viewId); + eventDispatcher.dispatchEvent(new OnAudioTracksEvent(viewId, audioTracks)); + } + + public void textTracks(ArrayList textTracks){ + EventDispatcher eventDispatcher = + UIManagerHelper.getEventDispatcherForReactTag(this.context, viewId); + eventDispatcher.dispatchEvent(new OnTextTracksEvent(viewId, textTracks)); + } + + public void videoTracks(ArrayList videoTracks){ + EventDispatcher eventDispatcher = + UIManagerHelper.getEventDispatcherForReactTag(this.context, viewId); + eventDispatcher.dispatchEvent(new OnVideoTracksEvent(viewId, videoTracks)); + } + + void progressChanged(double currentPosition, double bufferedDuration, double seekableDuration, double currentPlaybackTime) { + EventDispatcher eventDispatcher = + UIManagerHelper.getEventDispatcherForReactTag(this.context, viewId); + eventDispatcher.dispatchEvent(new OnVideoProgressEvent(viewId, currentPosition, bufferedDuration, seekableDuration, currentPlaybackTime)); + } + + void bandwidthReport(double bitRateEstimate, int height, int width, String id) { + EventDispatcher eventDispatcher = + UIManagerHelper.getEventDispatcherForReactTag(this.context, viewId); + eventDispatcher.dispatchEvent(new OnVideoBandwidthUpdateEvent(viewId, bitRateEstimate, height, width, id)); + } + + void seek(long currentPosition, long seekTime, boolean finished) { + EventDispatcher eventDispatcher = + UIManagerHelper.getEventDispatcherForReactTag(this.context, viewId); + eventDispatcher.dispatchEvent(new OnVideoSeekEvent(viewId, currentPosition, seekTime, finished)); + } + + void ready() { + EventDispatcher eventDispatcher = + UIManagerHelper.getEventDispatcherForReactTag(this.context, viewId); + eventDispatcher.dispatchEvent(new OnReadyForDisplayEvent(viewId)); + } + + void buffering(boolean isBuffering) { + EventDispatcher eventDispatcher = + UIManagerHelper.getEventDispatcherForReactTag(this.context, viewId); + eventDispatcher.dispatchEvent(new OnVideoBufferEvent(viewId, isBuffering)); + } + + void playbackStateChanged(boolean isPlaying) { + EventDispatcher eventDispatcher = + UIManagerHelper.getEventDispatcherForReactTag(this.context, viewId); + eventDispatcher.dispatchEvent(new OnVideoPlaybackStateChangedEvent(viewId, isPlaying)); + } + + void idle() { + EventDispatcher eventDispatcher = + UIManagerHelper.getEventDispatcherForReactTag(this.context, viewId); + eventDispatcher.dispatchEvent(new OnVideoIdleEvent(viewId)); + } + + void end() { + EventDispatcher eventDispatcher = + UIManagerHelper.getEventDispatcherForReactTag(this.context, viewId); + eventDispatcher.dispatchEvent(new OnVideoEndEvent(viewId)); + } + + void fullscreenWillPresent() { + EventDispatcher eventDispatcher = + UIManagerHelper.getEventDispatcherForReactTag(this.context, viewId); + eventDispatcher.dispatchEvent(new OnVideoFullscreenPlayerWillPresentEvent(viewId)); + } + + void fullscreenDidPresent() { + EventDispatcher eventDispatcher = + UIManagerHelper.getEventDispatcherForReactTag(this.context, viewId); + eventDispatcher.dispatchEvent(new OnVideoFullscreenPlayerDidPresentEvent(viewId)); + } + + void fullscreenWillDismiss() { + EventDispatcher eventDispatcher = + UIManagerHelper.getEventDispatcherForReactTag(this.context, viewId); + eventDispatcher.dispatchEvent(new OnVideoFullscreenPlayerWillDismissEvent(viewId)); + } + + void fullscreenDidDismiss() { + EventDispatcher eventDispatcher = + UIManagerHelper.getEventDispatcherForReactTag(this.context, viewId); + eventDispatcher.dispatchEvent(new OnVideoFullscreenPlayerDidDismissEvent(viewId)); + } + + void error(String errorString, Exception exception) { + _error(errorString, exception, "0001"); + } + + void error(String errorString, Exception exception, String errorCode) { + _error(errorString, exception, errorCode); + } + + void _error(String errorString, Exception exception, String errorCode) { + EventDispatcher eventDispatcher = + UIManagerHelper.getEventDispatcherForReactTag(this.context, viewId); + eventDispatcher.dispatchEvent(new OnVideoErrorEvent(viewId, errorString, exception, errorCode)); + } + + void playbackRateChange(float rate) { + EventDispatcher eventDispatcher = + UIManagerHelper.getEventDispatcherForReactTag(this.context, viewId); + eventDispatcher.dispatchEvent(new OnPlaybackRateChangeEvent(viewId, rate)); + } + + void timedMetadata(ArrayList metadata) { + EventDispatcher eventDispatcher = + UIManagerHelper.getEventDispatcherForReactTag(this.context, viewId); + eventDispatcher.dispatchEvent(new OnTimedMetadataEvent(viewId, metadata)); + } + + void audioFocusChanged(boolean hasFocus) { + EventDispatcher eventDispatcher = + UIManagerHelper.getEventDispatcherForReactTag(this.context, viewId); + eventDispatcher.dispatchEvent(new OnAudioFocusChangedEvent(viewId, hasFocus)); + } + + void audioBecomingNoisy() { + EventDispatcher eventDispatcher = + UIManagerHelper.getEventDispatcherForReactTag(this.context, viewId); + eventDispatcher.dispatchEvent(new OnVideoAudioBecomingNoisyEvent(viewId)); + } + + void receiveAdEvent(String event) { + EventDispatcher eventDispatcher = + UIManagerHelper.getEventDispatcherForReactTag(this.context, viewId); + eventDispatcher.dispatchEvent(new OnReceiveAdEventEvent(viewId, event)); + } +} diff --git a/android/src/fabric/java/com/brentvatne/exoplayer/events/OnAudioFocusChangedEvent.kt b/android/src/fabric/java/com/brentvatne/exoplayer/events/OnAudioFocusChangedEvent.kt new file mode 100644 index 0000000000..045e304c62 --- /dev/null +++ b/android/src/fabric/java/com/brentvatne/exoplayer/events/OnAudioFocusChangedEvent.kt @@ -0,0 +1,22 @@ +package com.brentvatne.exoplayer.events + +import com.facebook.react.bridge.Arguments +import com.facebook.react.uimanager.events.Event +import com.facebook.react.uimanager.events.RCTEventEmitter + +class OnAudioFocusChangedEvent(viewTag: Int, private val hasFocus: Boolean): Event(viewTag) { + private val EVENT_PROP_HAS_AUDIO_FOCUS = "hasAudioFocus" + override fun getEventName(): String { + return EVENT_NAME + } + + companion object { + const val EVENT_NAME = "topOnAudioFocusChanged" + } + + override fun dispatch(rctEventEmitter: RCTEventEmitter?) { + val event = Arguments.createMap() + event.putBoolean(EVENT_PROP_HAS_AUDIO_FOCUS, hasFocus) + rctEventEmitter?.receiveEvent(viewTag, getEventName(), event) + } +} \ No newline at end of file diff --git a/android/src/fabric/java/com/brentvatne/exoplayer/events/OnAudioTracksEvent.kt b/android/src/fabric/java/com/brentvatne/exoplayer/events/OnAudioTracksEvent.kt new file mode 100644 index 0000000000..f5ab13ce93 --- /dev/null +++ b/android/src/fabric/java/com/brentvatne/exoplayer/events/OnAudioTracksEvent.kt @@ -0,0 +1,48 @@ +package com.brentvatne.exoplayer.events + +import com.brentvatne.common.api.Track +import com.facebook.react.bridge.Arguments +import com.facebook.react.bridge.WritableArray +import com.facebook.react.bridge.WritableMap +import com.facebook.react.uimanager.events.Event +import com.facebook.react.uimanager.events.RCTEventEmitter + +class OnAudioTracksEvent(viewTag: Int, private val audioTracks: ArrayList) : Event(viewTag) { + private val EVENT_PROP_AUDIO_TRACKS = "audioTracks" + override fun getEventName(): String { + return EVENT_NAME + } + + companion object { + const val EVENT_NAME = "topOnAudioTracks" + } + + override fun dispatch(rctEventEmitter: RCTEventEmitter?) { + rctEventEmitter?.receiveEvent(viewTag, getEventName(), arrayToObject(EVENT_PROP_AUDIO_TRACKS, audioTracksToArray(audioTracks))) + } + + fun arrayToObject(field: String?, array: WritableArray?): WritableMap? { + val event = Arguments.createMap() + // @todo: temporarily remove put array on event callback parameter (codegen issue) + // event.putArray(field!!, array) + return event + } + + fun audioTracksToArray(audioTracks: java.util.ArrayList?): WritableArray? { + val waAudioTracks = Arguments.createArray() + if (audioTracks != null) { + for (i in audioTracks.indices) { + val format = audioTracks[i] + val audioTrack = Arguments.createMap() + audioTrack.putInt("index", i) + audioTrack.putString("title", if (format.title != null) format.title else "") + audioTrack.putString("type", if (format.mimeType != null) format.mimeType else "") + audioTrack.putString("language", if (format.language != null) format.language else "") + audioTrack.putInt("bitrate", format.bitrate) + audioTrack.putBoolean("selected", format.isSelected) + waAudioTracks.pushMap(audioTrack) + } + } + return waAudioTracks + } +} diff --git a/android/src/fabric/java/com/brentvatne/exoplayer/events/OnPlaybackRateChangeEvent.kt b/android/src/fabric/java/com/brentvatne/exoplayer/events/OnPlaybackRateChangeEvent.kt new file mode 100644 index 0000000000..5448a12239 --- /dev/null +++ b/android/src/fabric/java/com/brentvatne/exoplayer/events/OnPlaybackRateChangeEvent.kt @@ -0,0 +1,22 @@ +package com.brentvatne.exoplayer.events + +import com.facebook.react.bridge.Arguments +import com.facebook.react.uimanager.events.Event +import com.facebook.react.uimanager.events.RCTEventEmitter + +class OnPlaybackRateChangeEvent(viewTag: Int, private val rate: Float): Event(viewTag) { + private val EVENT_PROP_PLAYBACK_RATE = "playbackRate" + override fun getEventName(): String { + return EVENT_NAME + } + + companion object { + const val EVENT_NAME = "topOnPlaybackRateChange" + } + + override fun dispatch(rctEventEmitter: RCTEventEmitter?) { + val event = Arguments.createMap() + event.putDouble(EVENT_PROP_PLAYBACK_RATE, rate.toDouble()) + rctEventEmitter?.receiveEvent(viewTag, getEventName(), event) + } +} \ No newline at end of file diff --git a/android/src/fabric/java/com/brentvatne/exoplayer/events/OnReadyForDisplayEvent.kt b/android/src/fabric/java/com/brentvatne/exoplayer/events/OnReadyForDisplayEvent.kt new file mode 100644 index 0000000000..c193b237ea --- /dev/null +++ b/android/src/fabric/java/com/brentvatne/exoplayer/events/OnReadyForDisplayEvent.kt @@ -0,0 +1,19 @@ +package com.brentvatne.exoplayer.events + +import com.facebook.react.bridge.Arguments +import com.facebook.react.uimanager.events.Event +import com.facebook.react.uimanager.events.RCTEventEmitter + +class OnReadyForDisplayEvent(viewTag: Int) : Event(viewTag) { + override fun getEventName(): String { + return EVENT_NAME + } + + companion object { + const val EVENT_NAME = "topOnReadyForDisplay" + } + + override fun dispatch(rctEventEmitter: RCTEventEmitter?) { + rctEventEmitter?.receiveEvent(viewTag, getEventName(), Arguments.createMap()) + } +} \ No newline at end of file diff --git a/android/src/fabric/java/com/brentvatne/exoplayer/events/OnReceiveAdEventEvent.kt b/android/src/fabric/java/com/brentvatne/exoplayer/events/OnReceiveAdEventEvent.kt new file mode 100644 index 0000000000..b65085db0a --- /dev/null +++ b/android/src/fabric/java/com/brentvatne/exoplayer/events/OnReceiveAdEventEvent.kt @@ -0,0 +1,21 @@ +package com.brentvatne.exoplayer.events + +import com.facebook.react.bridge.Arguments +import com.facebook.react.uimanager.events.Event +import com.facebook.react.uimanager.events.RCTEventEmitter + +class OnReceiveAdEventEvent(viewTag: Int, private val event: String): Event(viewTag) { + override fun getEventName(): String { + return EVENT_NAME + } + + companion object { + const val EVENT_NAME = "topOnReceiveAdEventEvent" + } + + override fun dispatch(rctEventEmitter: RCTEventEmitter?) { + val map = Arguments.createMap() + map.putString("event", event) + rctEventEmitter?.receiveEvent(viewTag, getEventName(), map) + } +} \ No newline at end of file diff --git a/android/src/fabric/java/com/brentvatne/exoplayer/events/OnTextTracksEvent.kt b/android/src/fabric/java/com/brentvatne/exoplayer/events/OnTextTracksEvent.kt new file mode 100644 index 0000000000..a5d6439e33 --- /dev/null +++ b/android/src/fabric/java/com/brentvatne/exoplayer/events/OnTextTracksEvent.kt @@ -0,0 +1,47 @@ +package com.brentvatne.exoplayer.events + +import com.brentvatne.common.api.Track +import com.facebook.react.bridge.Arguments +import com.facebook.react.bridge.WritableArray +import com.facebook.react.bridge.WritableMap +import com.facebook.react.uimanager.events.Event +import com.facebook.react.uimanager.events.RCTEventEmitter + +class OnTextTracksEvent(viewTag: Int, private val textTracks: ArrayList) : Event(viewTag) { + private val EVENT_PROP_TEXT_TRACKS = "textTracks" + override fun getEventName(): String { + return EVENT_NAME + } + + companion object { + const val EVENT_NAME = "topOnTextTracks" + } + + override fun dispatch(rctEventEmitter: RCTEventEmitter?) { + rctEventEmitter?.receiveEvent(viewTag, getEventName(), arrayToObject(EVENT_PROP_TEXT_TRACKS, textTracksToArray(textTracks))) + } + + fun arrayToObject(field: String?, array: WritableArray?): WritableMap? { + val event = Arguments.createMap() + // @todo: temporarily remove put array on event callback parameter (codegen issue) + // event.putArray(field!!, array) + return event + } + + fun textTracksToArray(textTracks: ArrayList?): WritableArray? { + val waTextTracks = Arguments.createArray() + if (textTracks != null) { + for (i in textTracks.indices) { + val format = textTracks[i] + val textTrack = Arguments.createMap() + textTrack.putInt("index", i) + textTrack.putString("title", if (format.title != null) format.title else "") + textTrack.putString("type", if (format.mimeType != null) format.mimeType else "") + textTrack.putString("language", if (format.language != null) format.language else "") + textTrack.putBoolean("selected", format.isSelected) + waTextTracks.pushMap(textTrack) + } + } + return waTextTracks + } +} diff --git a/android/src/fabric/java/com/brentvatne/exoplayer/events/OnTimedMetadataEvent.kt b/android/src/fabric/java/com/brentvatne/exoplayer/events/OnTimedMetadataEvent.kt new file mode 100644 index 0000000000..9344795b8e --- /dev/null +++ b/android/src/fabric/java/com/brentvatne/exoplayer/events/OnTimedMetadataEvent.kt @@ -0,0 +1,30 @@ +package com.brentvatne.exoplayer.events + +import androidx.media3.common.Metadata +import androidx.media3.extractor.metadata.emsg.EventMessage +import androidx.media3.extractor.metadata.id3.Id3Frame +import androidx.media3.extractor.metadata.id3.TextInformationFrame +import com.brentvatne.common.api.TimedMetadata +import com.facebook.react.bridge.Arguments +import com.facebook.react.uimanager.events.Event +import com.facebook.react.uimanager.events.RCTEventEmitter +import java.util.ArrayList + +class OnTimedMetadataEvent(viewTag: Int, private val metadata: ArrayList): Event(viewTag) { + private val EVENT_PROP_TIMED_METADATA = "metadata" + override fun getEventName(): String { + return EVENT_NAME + } + + companion object { + const val EVENT_NAME = "topOnTimedMetadata" + } + + override fun dispatch(rctEventEmitter: RCTEventEmitter?) { + + val event = Arguments.createMap() + // @todo: temporarily remove put array on event callback parameter (codegen issue) + // event.putArray(EVENT_PROP_TIMED_METADATA, metadataArray) + rctEventEmitter?.receiveEvent(viewTag, getEventName(), event) + } +} diff --git a/android/src/fabric/java/com/brentvatne/exoplayer/events/OnVideoAudioBecomingNoisyEvent.kt b/android/src/fabric/java/com/brentvatne/exoplayer/events/OnVideoAudioBecomingNoisyEvent.kt new file mode 100644 index 0000000000..eb81aa9bbf --- /dev/null +++ b/android/src/fabric/java/com/brentvatne/exoplayer/events/OnVideoAudioBecomingNoisyEvent.kt @@ -0,0 +1,19 @@ +package com.brentvatne.exoplayer.events + +import com.facebook.react.bridge.Arguments +import com.facebook.react.uimanager.events.Event +import com.facebook.react.uimanager.events.RCTEventEmitter + +class OnVideoAudioBecomingNoisyEvent(viewTag: Int): Event(viewTag) { + override fun getEventName(): String { + return EVENT_NAME + } + + companion object { + const val EVENT_NAME = "topOnVideoAudioBecomingNoisy" + } + + override fun dispatch(rctEventEmitter: RCTEventEmitter?) { + rctEventEmitter?.receiveEvent(viewTag, getEventName(), Arguments.createMap()) + } +} \ No newline at end of file diff --git a/android/src/fabric/java/com/brentvatne/exoplayer/events/OnVideoBandwidthUpdateEvent.kt b/android/src/fabric/java/com/brentvatne/exoplayer/events/OnVideoBandwidthUpdateEvent.kt new file mode 100644 index 0000000000..ea97205116 --- /dev/null +++ b/android/src/fabric/java/com/brentvatne/exoplayer/events/OnVideoBandwidthUpdateEvent.kt @@ -0,0 +1,34 @@ +package com.brentvatne.exoplayer.events + +import com.facebook.react.bridge.Arguments +import com.facebook.react.uimanager.events.Event +import com.facebook.react.uimanager.events.RCTEventEmitter + +class OnVideoBandwidthUpdateEvent( + viewTag: Int, + private val bitRateEstimate: Double, + private val height: Int, + private val width: Int, + private val id: String +) : Event(viewTag) { + private val EVENT_PROP_BITRATE = "bitrate" + private val EVENT_PROP_WIDTH = "width" + private val EVENT_PROP_HEIGHT = "height" + private val EVENT_PROP_TRACK_ID = "trackId" + override fun getEventName(): String { + return EVENT_NAME + } + + companion object { + const val EVENT_NAME = "topOnVideoBandwidthUpdate" + } + + override fun dispatch(rctEventEmitter: RCTEventEmitter?) { + val event = Arguments.createMap() + event.putDouble(EVENT_PROP_BITRATE, bitRateEstimate) + event.putInt(EVENT_PROP_WIDTH, width) + event.putInt(EVENT_PROP_HEIGHT, height) + event.putString(EVENT_PROP_TRACK_ID, id) + rctEventEmitter?.receiveEvent(viewTag, getEventName(), event) + } +} \ No newline at end of file diff --git a/android/src/fabric/java/com/brentvatne/exoplayer/events/OnVideoBufferEvent.kt b/android/src/fabric/java/com/brentvatne/exoplayer/events/OnVideoBufferEvent.kt new file mode 100644 index 0000000000..110c92758a --- /dev/null +++ b/android/src/fabric/java/com/brentvatne/exoplayer/events/OnVideoBufferEvent.kt @@ -0,0 +1,22 @@ +package com.brentvatne.exoplayer.events + +import com.facebook.react.bridge.Arguments +import com.facebook.react.uimanager.events.Event +import com.facebook.react.uimanager.events.RCTEventEmitter + +class OnVideoBufferEvent(viewTag: Int, private val isBuffering: Boolean) : Event(viewTag) { + private val EVENT_PROP_IS_BUFFERING = "isBuffering" + override fun getEventName(): String { + return EVENT_NAME + } + + companion object { + const val EVENT_NAME = "topOnVideoBuffer" + } + + override fun dispatch(rctEventEmitter: RCTEventEmitter?) { + val event = Arguments.createMap() + event.putBoolean(EVENT_PROP_IS_BUFFERING, isBuffering) + rctEventEmitter?.receiveEvent(viewTag, getEventName(), event) + } +} \ No newline at end of file diff --git a/android/src/fabric/java/com/brentvatne/exoplayer/events/OnVideoEndEvent.kt b/android/src/fabric/java/com/brentvatne/exoplayer/events/OnVideoEndEvent.kt new file mode 100644 index 0000000000..8b1d8abc04 --- /dev/null +++ b/android/src/fabric/java/com/brentvatne/exoplayer/events/OnVideoEndEvent.kt @@ -0,0 +1,19 @@ +package com.brentvatne.exoplayer.events + +import com.facebook.react.bridge.Arguments +import com.facebook.react.uimanager.events.Event +import com.facebook.react.uimanager.events.RCTEventEmitter + +class OnVideoEndEvent(viewTag: Int): Event(viewTag) { + override fun getEventName(): String { + return EVENT_NAME + } + + companion object { + const val EVENT_NAME = "topOnVideoEnd" + } + + override fun dispatch(rctEventEmitter: RCTEventEmitter?) { + rctEventEmitter?.receiveEvent(viewTag, getEventName(), Arguments.createMap()) + } +} \ No newline at end of file diff --git a/android/src/fabric/java/com/brentvatne/exoplayer/events/OnVideoErrorEvent.kt b/android/src/fabric/java/com/brentvatne/exoplayer/events/OnVideoErrorEvent.kt new file mode 100644 index 0000000000..bac4df8a8c --- /dev/null +++ b/android/src/fabric/java/com/brentvatne/exoplayer/events/OnVideoErrorEvent.kt @@ -0,0 +1,46 @@ +package com.brentvatne.exoplayer.events + +import com.facebook.react.bridge.Arguments +import com.facebook.react.uimanager.events.Event +import com.facebook.react.uimanager.events.RCTEventEmitter +import java.io.PrintWriter +import java.io.StringWriter + +class OnVideoErrorEvent( + viewTag: Int, + private val errorString: String, + private val exception: Exception, + private val errorCode: String +): Event(viewTag) { + private val EVENT_PROP_ERROR = "error" + private val EVENT_PROP_ERROR_STRING = "errorString" + private val EVENT_PROP_ERROR_EXCEPTION = "errorException" + private val EVENT_PROP_ERROR_TRACE = "errorStackTrace" + private val EVENT_PROP_ERROR_CODE = "errorCode" + override fun getEventName(): String { + return EVENT_NAME + } + + companion object { + const val EVENT_NAME = "topOnVideoErrorEvent" + } + + override fun dispatch(rctEventEmitter: RCTEventEmitter?) { + // Prepare stack trace + + // Prepare stack trace + val sw = StringWriter() + val pw = PrintWriter(sw) + exception.printStackTrace(pw) + val stackTrace = sw.toString() + + val error = Arguments.createMap() + error.putString(EVENT_PROP_ERROR_STRING, errorString) + error.putString(EVENT_PROP_ERROR_EXCEPTION, exception.toString()) + error.putString(EVENT_PROP_ERROR_CODE, errorCode) + error.putString(EVENT_PROP_ERROR_TRACE, stackTrace) + val event = Arguments.createMap() + event.putMap(EVENT_PROP_ERROR, error) + rctEventEmitter?.receiveEvent(viewTag, getEventName(), event) + } +} \ No newline at end of file diff --git a/android/src/fabric/java/com/brentvatne/exoplayer/events/OnVideoFullscreenPlayerDidDismissEvent.kt b/android/src/fabric/java/com/brentvatne/exoplayer/events/OnVideoFullscreenPlayerDidDismissEvent.kt new file mode 100644 index 0000000000..10ba57e099 --- /dev/null +++ b/android/src/fabric/java/com/brentvatne/exoplayer/events/OnVideoFullscreenPlayerDidDismissEvent.kt @@ -0,0 +1,19 @@ +package com.brentvatne.exoplayer.events + +import com.facebook.react.bridge.Arguments +import com.facebook.react.uimanager.events.Event +import com.facebook.react.uimanager.events.RCTEventEmitter + +class OnVideoFullscreenPlayerDidDismissEvent(viewTag: Int): Event(viewTag) { + override fun getEventName(): String { + return EVENT_NAME + } + + companion object { + const val EVENT_NAME = "topOnVideoFullscreenPlayerDidDismiss" + } + + override fun dispatch(rctEventEmitter: RCTEventEmitter?) { + rctEventEmitter?.receiveEvent(viewTag, getEventName(), Arguments.createMap()) + } +} \ No newline at end of file diff --git a/android/src/fabric/java/com/brentvatne/exoplayer/events/OnVideoFullscreenPlayerDidPresentEvent.kt b/android/src/fabric/java/com/brentvatne/exoplayer/events/OnVideoFullscreenPlayerDidPresentEvent.kt new file mode 100644 index 0000000000..a852948666 --- /dev/null +++ b/android/src/fabric/java/com/brentvatne/exoplayer/events/OnVideoFullscreenPlayerDidPresentEvent.kt @@ -0,0 +1,19 @@ +package com.brentvatne.exoplayer.events + +import com.facebook.react.bridge.Arguments +import com.facebook.react.uimanager.events.Event +import com.facebook.react.uimanager.events.RCTEventEmitter + +class OnVideoFullscreenPlayerDidPresentEvent(viewTag: Int): Event(viewTag) { + override fun getEventName(): String { + return EVENT_NAME + } + + companion object { + const val EVENT_NAME = "topOnVideoFullscreenPlayerDidPresent" + } + + override fun dispatch(rctEventEmitter: RCTEventEmitter?) { + rctEventEmitter?.receiveEvent(viewTag, getEventName(), Arguments.createMap()) + } +} \ No newline at end of file diff --git a/android/src/fabric/java/com/brentvatne/exoplayer/events/OnVideoFullscreenPlayerWillDismissEvent.kt b/android/src/fabric/java/com/brentvatne/exoplayer/events/OnVideoFullscreenPlayerWillDismissEvent.kt new file mode 100644 index 0000000000..5fc690cfb8 --- /dev/null +++ b/android/src/fabric/java/com/brentvatne/exoplayer/events/OnVideoFullscreenPlayerWillDismissEvent.kt @@ -0,0 +1,19 @@ +package com.brentvatne.exoplayer.events + +import com.facebook.react.bridge.Arguments +import com.facebook.react.uimanager.events.Event +import com.facebook.react.uimanager.events.RCTEventEmitter + +class OnVideoFullscreenPlayerWillDismissEvent(viewTag: Int): Event(viewTag) { + override fun getEventName(): String { + return EVENT_NAME + } + + companion object { + const val EVENT_NAME = "topOnVideoFullscreenPlayerWillDismiss" + } + + override fun dispatch(rctEventEmitter: RCTEventEmitter?) { + rctEventEmitter?.receiveEvent(viewTag, getEventName(), Arguments.createMap()) + } +} \ No newline at end of file diff --git a/android/src/fabric/java/com/brentvatne/exoplayer/events/OnVideoFullscreenPlayerWillPresentEvent.kt b/android/src/fabric/java/com/brentvatne/exoplayer/events/OnVideoFullscreenPlayerWillPresentEvent.kt new file mode 100644 index 0000000000..c10cfbc39a --- /dev/null +++ b/android/src/fabric/java/com/brentvatne/exoplayer/events/OnVideoFullscreenPlayerWillPresentEvent.kt @@ -0,0 +1,19 @@ +package com.brentvatne.exoplayer.events + +import com.facebook.react.bridge.Arguments +import com.facebook.react.uimanager.events.Event +import com.facebook.react.uimanager.events.RCTEventEmitter + +class OnVideoFullscreenPlayerWillPresentEvent(viewTag: Int): Event(viewTag) { + override fun getEventName(): String { + return EVENT_NAME + } + + companion object { + const val EVENT_NAME = "topOnVideoFullscreenPlayerWillPresent" + } + + override fun dispatch(rctEventEmitter: RCTEventEmitter?) { + rctEventEmitter?.receiveEvent(viewTag, getEventName(), Arguments.createMap()) + } +} \ No newline at end of file diff --git a/android/src/fabric/java/com/brentvatne/exoplayer/events/OnVideoIdleEvent.kt b/android/src/fabric/java/com/brentvatne/exoplayer/events/OnVideoIdleEvent.kt new file mode 100644 index 0000000000..dc265b6f87 --- /dev/null +++ b/android/src/fabric/java/com/brentvatne/exoplayer/events/OnVideoIdleEvent.kt @@ -0,0 +1,19 @@ +package com.brentvatne.exoplayer.events + +import com.facebook.react.bridge.Arguments +import com.facebook.react.uimanager.events.Event +import com.facebook.react.uimanager.events.RCTEventEmitter + +class OnVideoIdleEvent(viewTag: Int): Event(viewTag) { + override fun getEventName(): String { + return EVENT_NAME + } + + companion object { + const val EVENT_NAME = "topOnVideoIdle" + } + + override fun dispatch(rctEventEmitter: RCTEventEmitter?) { + rctEventEmitter?.receiveEvent(viewTag, getEventName(), Arguments.createMap()) + } +} \ No newline at end of file diff --git a/android/src/fabric/java/com/brentvatne/exoplayer/events/OnVideoLoadEvent.kt b/android/src/fabric/java/com/brentvatne/exoplayer/events/OnVideoLoadEvent.kt new file mode 100644 index 0000000000..7527a6d613 --- /dev/null +++ b/android/src/fabric/java/com/brentvatne/exoplayer/events/OnVideoLoadEvent.kt @@ -0,0 +1,104 @@ +package com.brentvatne.exoplayer.events + +import com.facebook.react.bridge.Arguments +import com.facebook.react.bridge.WritableArray +import com.facebook.react.bridge.WritableMap +import com.facebook.react.uimanager.events.Event +import com.facebook.react.uimanager.events.RCTEventEmitter + +class OnVideoLoadEvent(viewTag: Int, + private val duration: Double, + private val currentPosition: Double, + private val videoWidth: Int, + private val videoHeight: Int, + private val audioTracks: WritableArray, + private val textTracks: WritableArray, + private val videoTracks: WritableArray, + private val trackId: String): Event(viewTag) { + private val EVENT_PROP_FAST_FORWARD = "canPlayFastForward" + private val EVENT_PROP_SLOW_FORWARD = "canPlaySlowForward" + private val EVENT_PROP_SLOW_REVERSE = "canPlaySlowReverse" + private val EVENT_PROP_REVERSE = "canPlayReverse" + private val EVENT_PROP_STEP_FORWARD = "canStepForward" + private val EVENT_PROP_STEP_BACKWARD = "canStepBackward" + + private val EVENT_PROP_BUFFER_START = "bufferStart" + private val EVENT_PROP_BUFFER_END = "bufferEnd" + private val EVENT_PROP_DURATION = "duration" + private val EVENT_PROP_PLAYABLE_DURATION = "playableDuration" + private val EVENT_PROP_SEEKABLE_DURATION = "seekableDuration" + private val EVENT_PROP_CURRENT_TIME = "currentTime" + private val EVENT_PROP_CURRENT_PLAYBACK_TIME = "currentPlaybackTime" + private val EVENT_PROP_SEEK_TIME = "seekTime" + private val EVENT_PROP_NATURAL_SIZE = "naturalSize" + private val EVENT_PROP_TRACK_ID = "trackId" + private val EVENT_PROP_WIDTH = "width" + private val EVENT_PROP_HEIGHT = "height" + private val EVENT_PROP_ORIENTATION = "orientation" + private val EVENT_PROP_VIDEO_TRACKS = "videoTracks" + private val EVENT_PROP_AUDIO_TRACKS = "audioTracks" + private val EVENT_PROP_TEXT_TRACKS = "textTracks" + private val EVENT_PROP_HAS_AUDIO_FOCUS = "hasAudioFocus" + private val EVENT_PROP_IS_BUFFERING = "isBuffering" + private val EVENT_PROP_PLAYBACK_RATE = "playbackRate" + + private val EVENT_PROP_ERROR = "error" + private val EVENT_PROP_ERROR_STRING = "errorString" + private val EVENT_PROP_ERROR_EXCEPTION = "errorException" + private val EVENT_PROP_ERROR_TRACE = "errorStackTrace" + private val EVENT_PROP_ERROR_CODE = "errorCode" + + private val EVENT_PROP_TIMED_METADATA = "metadata" + + private val EVENT_PROP_BITRATE = "bitrate" + + private val EVENT_PROP_IS_PLAYING = "isPlaying" + + override fun getEventName(): String { + return EVENT_NAME + } + + override fun dispatch(rctEventEmitter: RCTEventEmitter?) { + val event = Arguments.createMap() + event.putDouble(EVENT_PROP_DURATION, duration / 1000.0) + event.putDouble(EVENT_PROP_CURRENT_TIME, currentPosition / 1000.0) + + val naturalSize: WritableMap? = aspectRatioToNaturalSize(videoWidth, videoHeight) + event.putMap(EVENT_PROP_NATURAL_SIZE, naturalSize) + event.putString(EVENT_PROP_TRACK_ID, trackId) + // @todo: temporarily remove put array on event callback parameter (codegen issue) + // event.putArray(EVENT_PROP_VIDEO_TRACKS, videoTracks) + // event.putArray(EVENT_PROP_AUDIO_TRACKS, audioTracks) + // event.putArray(EVENT_PROP_TEXT_TRACKS, textTracks) + + // TODO: Actually check if you can. + + // 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) + rctEventEmitter?.receiveEvent(viewTag, getEventName(), event) + } + + companion object { + const val EVENT_NAME = "topOnVideoLoad" + } + + fun aspectRatioToNaturalSize(videoWidth: Int, videoHeight: Int): WritableMap? { + val naturalSize = Arguments.createMap() + naturalSize.putInt(EVENT_PROP_WIDTH, videoWidth) + naturalSize.putInt(EVENT_PROP_HEIGHT, videoHeight) + if (videoWidth > videoHeight) { + naturalSize.putString(EVENT_PROP_ORIENTATION, "landscape") + } else if (videoWidth < videoHeight) { + naturalSize.putString(EVENT_PROP_ORIENTATION, "portrait") + } else { + naturalSize.putString(EVENT_PROP_ORIENTATION, "square") + } + return naturalSize + } +} \ No newline at end of file diff --git a/android/src/fabric/java/com/brentvatne/exoplayer/events/OnVideoLoadStartEvent.kt b/android/src/fabric/java/com/brentvatne/exoplayer/events/OnVideoLoadStartEvent.kt new file mode 100644 index 0000000000..0a116b6390 --- /dev/null +++ b/android/src/fabric/java/com/brentvatne/exoplayer/events/OnVideoLoadStartEvent.kt @@ -0,0 +1,19 @@ +package com.brentvatne.exoplayer.events + +import com.facebook.react.bridge.Arguments +import com.facebook.react.uimanager.events.Event +import com.facebook.react.uimanager.events.RCTEventEmitter + +class OnVideoLoadStartEvent(viewTag: Int) : Event(viewTag) { + override fun getEventName(): String { + return EVENT_NAME + } + + override fun dispatch(rctEventEmitter: RCTEventEmitter?) { + rctEventEmitter?.receiveEvent(viewTag, getEventName(), Arguments.createMap()) + } + + companion object { + const val EVENT_NAME = "topOnVideoLoadStart" + } +} \ No newline at end of file diff --git a/android/src/fabric/java/com/brentvatne/exoplayer/events/OnVideoPlaybackStateChangedEvent.kt b/android/src/fabric/java/com/brentvatne/exoplayer/events/OnVideoPlaybackStateChangedEvent.kt new file mode 100644 index 0000000000..50e7b0832c --- /dev/null +++ b/android/src/fabric/java/com/brentvatne/exoplayer/events/OnVideoPlaybackStateChangedEvent.kt @@ -0,0 +1,22 @@ +package com.brentvatne.exoplayer.events + +import com.facebook.react.bridge.Arguments +import com.facebook.react.uimanager.events.Event +import com.facebook.react.uimanager.events.RCTEventEmitter + +class OnVideoPlaybackStateChangedEvent(viewTag: Int, private val isPlaying: Boolean) : Event(viewTag) { + private val EVENT_PROP_IS_PLAYING = "isPlaying" + override fun getEventName(): String { + return EVENT_NAME + } + + companion object { + const val EVENT_NAME = "topOnVideoPlaybackStateChanged" + } + + override fun dispatch(rctEventEmitter: RCTEventEmitter?) { + val event = Arguments.createMap() + event.putBoolean(EVENT_PROP_IS_PLAYING, isPlaying) + rctEventEmitter?.receiveEvent(viewTag, getEventName(), event) + } +} \ No newline at end of file diff --git a/android/src/fabric/java/com/brentvatne/exoplayer/events/OnVideoProgressEvent.kt b/android/src/fabric/java/com/brentvatne/exoplayer/events/OnVideoProgressEvent.kt new file mode 100644 index 0000000000..5dec7cdc70 --- /dev/null +++ b/android/src/fabric/java/com/brentvatne/exoplayer/events/OnVideoProgressEvent.kt @@ -0,0 +1,69 @@ +package com.brentvatne.exoplayer.events + +import com.facebook.react.bridge.Arguments +import com.facebook.react.uimanager.events.Event +import com.facebook.react.uimanager.events.RCTEventEmitter + +class OnVideoProgressEvent( + viewTag: Int, + private val currentPosition: Double, + private val bufferedDuration: Double, + private val seekableDuration: Double, + private val currentPlaybackTime: Double +): Event(viewTag) { + private val EVENT_PROP_FAST_FORWARD = "canPlayFastForward" + private val EVENT_PROP_SLOW_FORWARD = "canPlaySlowForward" + private val EVENT_PROP_SLOW_REVERSE = "canPlaySlowReverse" + private val EVENT_PROP_REVERSE = "canPlayReverse" + private val EVENT_PROP_STEP_FORWARD = "canStepForward" + private val EVENT_PROP_STEP_BACKWARD = "canStepBackward" + + private val EVENT_PROP_BUFFER_START = "bufferStart" + private val EVENT_PROP_BUFFER_END = "bufferEnd" + private val EVENT_PROP_DURATION = "duration" + private val EVENT_PROP_PLAYABLE_DURATION = "playableDuration" + private val EVENT_PROP_SEEKABLE_DURATION = "seekableDuration" + private val EVENT_PROP_CURRENT_TIME = "currentTime" + private val EVENT_PROP_CURRENT_PLAYBACK_TIME = "currentPlaybackTime" + private val EVENT_PROP_SEEK_TIME = "seekTime" + private val EVENT_PROP_NATURAL_SIZE = "naturalSize" + private val EVENT_PROP_TRACK_ID = "trackId" + private val EVENT_PROP_WIDTH = "width" + private val EVENT_PROP_HEIGHT = "height" + private val EVENT_PROP_ORIENTATION = "orientation" + private val EVENT_PROP_VIDEO_TRACKS = "videoTracks" + private val EVENT_PROP_AUDIO_TRACKS = "audioTracks" + private val EVENT_PROP_TEXT_TRACKS = "textTracks" + private val EVENT_PROP_HAS_AUDIO_FOCUS = "hasAudioFocus" + private val EVENT_PROP_IS_BUFFERING = "isBuffering" + private val EVENT_PROP_PLAYBACK_RATE = "playbackRate" + + private val EVENT_PROP_ERROR = "error" + private val EVENT_PROP_ERROR_STRING = "errorString" + private val EVENT_PROP_ERROR_EXCEPTION = "errorException" + private val EVENT_PROP_ERROR_TRACE = "errorStackTrace" + private val EVENT_PROP_ERROR_CODE = "errorCode" + + private val EVENT_PROP_TIMED_METADATA = "metadata" + + private val EVENT_PROP_BITRATE = "bitrate" + + private val EVENT_PROP_IS_PLAYING = "isPlaying" + + override fun getEventName(): String { + return EVENT_NAME + } + + override fun dispatch(rctEventEmitter: RCTEventEmitter?) { + val event = Arguments.createMap() + event.putDouble(EVENT_PROP_CURRENT_TIME, currentPosition / 1000.0) + event.putDouble(EVENT_PROP_PLAYABLE_DURATION, bufferedDuration / 1000.0) + event.putDouble(EVENT_PROP_SEEKABLE_DURATION, seekableDuration / 1000.0) + event.putDouble(EVENT_PROP_CURRENT_PLAYBACK_TIME, currentPlaybackTime) + rctEventEmitter?.receiveEvent(viewTag, getEventName(), event) + } + + companion object { + const val EVENT_NAME = "topOnVideoProgress" + } +} \ No newline at end of file diff --git a/android/src/fabric/java/com/brentvatne/exoplayer/events/OnVideoSeekEvent.kt b/android/src/fabric/java/com/brentvatne/exoplayer/events/OnVideoSeekEvent.kt new file mode 100644 index 0000000000..120acdd15f --- /dev/null +++ b/android/src/fabric/java/com/brentvatne/exoplayer/events/OnVideoSeekEvent.kt @@ -0,0 +1,26 @@ +package com.brentvatne.exoplayer.events + +import com.facebook.react.bridge.Arguments +import com.facebook.react.uimanager.events.Event +import com.facebook.react.uimanager.events.RCTEventEmitter + +class OnVideoSeekEvent(viewTag: Int, private val currentPosition: Long, private val seekTime: Long, private val finished: Boolean) : Event(viewTag) { + private val EVENT_PROP_CURRENT_TIME = "currentTime" + private val EVENT_PROP_SEEK_TIME = "seekTime" + private val EVENT_PROP_FINISHED = "finished" + override fun getEventName(): String { + return EVENT_NAME + } + + override fun dispatch(rctEventEmitter: RCTEventEmitter?) { + val event = Arguments.createMap() + event.putDouble(EVENT_PROP_CURRENT_TIME, currentPosition / 1000.0) + event.putDouble(EVENT_PROP_SEEK_TIME, seekTime / 1000.0) + event.putBoolean(EVENT_PROP_FINISHED, finished) + rctEventEmitter?.receiveEvent(viewTag, getEventName(), event) + } + + companion object { + const val EVENT_NAME = "topOnVideoSeek" + } +} \ No newline at end of file diff --git a/android/src/fabric/java/com/brentvatne/exoplayer/events/OnVideoTracksEvent.kt b/android/src/fabric/java/com/brentvatne/exoplayer/events/OnVideoTracksEvent.kt new file mode 100644 index 0000000000..7973430e52 --- /dev/null +++ b/android/src/fabric/java/com/brentvatne/exoplayer/events/OnVideoTracksEvent.kt @@ -0,0 +1,48 @@ +package com.brentvatne.exoplayer.events + +import com.brentvatne.common.api.VideoTrack +import com.facebook.react.bridge.Arguments +import com.facebook.react.bridge.WritableArray +import com.facebook.react.bridge.WritableMap +import com.facebook.react.uimanager.events.Event +import com.facebook.react.uimanager.events.RCTEventEmitter + +class OnVideoTracksEvent(viewTag: Int, private val videoTracks: ArrayList) : Event(viewTag) { + private val EVENT_PROP_VIDEO_TRACKS = "videoTracks" + override fun getEventName(): String { + return EVENT_NAME + } + + companion object { + const val EVENT_NAME = "topOnVideoTracks" + } + + override fun dispatch(rctEventEmitter: RCTEventEmitter?) { + rctEventEmitter?.receiveEvent(viewTag, getEventName(), arrayToObject(EVENT_PROP_VIDEO_TRACKS, videoTracksToArray(videoTracks))) + } + + fun arrayToObject(field: String?, array: WritableArray?): WritableMap? { + val event = Arguments.createMap() + // @todo: temporarily remove put array on event callback parameter (codegen issue) + // event.putArray(field!!, array) + return event + } + + fun videoTracksToArray(videoTracks: ArrayList?): WritableArray? { + val waVideoTracks = Arguments.createArray() + if (videoTracks != null) { + for (i in videoTracks.indices) { + val vTrack = videoTracks[i] + val videoTrack = Arguments.createMap() + videoTrack.putInt("width", vTrack.width) + videoTrack.putInt("height", vTrack.height) + videoTrack.putInt("bitrate", vTrack.bitrate) + videoTrack.putString("codecs", vTrack.codecs) + videoTrack.putInt("trackId", vTrack.id) + videoTrack.putBoolean("selected", vTrack.isSelected) + waVideoTracks.pushMap(videoTrack) + } + } + return waVideoTracks + } +} diff --git a/android/src/main/java/com/brentvatne/exoplayer/ReactExoplayerView.java b/android/src/main/java/com/brentvatne/exoplayer/ReactExoplayerView.java index fef6d2479a..c3be79b084 100644 --- a/android/src/main/java/com/brentvatne/exoplayer/ReactExoplayerView.java +++ b/android/src/main/java/com/brentvatne/exoplayer/ReactExoplayerView.java @@ -96,7 +96,6 @@ import com.brentvatne.common.api.TimedMetadata; import com.brentvatne.common.api.Track; import com.brentvatne.common.api.VideoTrack; -import com.brentvatne.common.react.VideoEventEmitter; import com.brentvatne.common.toolbox.DebugLog; import com.brentvatne.react.R; import com.brentvatne.receiver.AudioBecomingNoisyReceiver; @@ -1397,8 +1396,9 @@ public void onTimelineChanged(@NonNull Timeline timeline, int reason) { @Override public void onPlaybackStateChanged(int playbackState) { + // seek finished if (playbackState == Player.STATE_READY && seekTime != C.TIME_UNSET) { - eventEmitter.seek(player.getCurrentPosition(), seekTime); + eventEmitter.seek(player.getCurrentPosition(), seekTime, true); seekTime = C.TIME_UNSET; if (isUsingContentResolution) { // We need to update the selected track to make sure that it still matches user selection if track list has changed in this period @@ -1431,8 +1431,9 @@ public void onPlaybackParametersChanged(PlaybackParameters params) { @Override public void onVolumeChanged(float volume) { - eventEmitter.volumeChange(volume); - } +// todo +// eventEmitter.volumeChange(volume); + }; @Override public void onIsPlayingChanged(boolean isPlaying) { @@ -1882,8 +1883,9 @@ public void setVolumeModifier(float volume) { public void seekTo(long positionMs) { if (player != null) { + seekTime = positionMs; + eventEmitter.seek(player.getCurrentPosition(), seekTime, false); player.seekTo(positionMs); - eventEmitter.seek(player.getCurrentPosition(), positionMs); } } @@ -2102,7 +2104,7 @@ public void setShutterColor(Integer color) { @Override public void onAdEvent(AdEvent adEvent) { if (adEvent.getAdData() != null) { - eventEmitter.receiveAdEvent(adEvent.getType().name(), adEvent.getAdData()); + eventEmitter.receiveAdEvent(adEvent.getType().name()); } else { eventEmitter.receiveAdEvent(adEvent.getType().name()); } @@ -2110,6 +2112,7 @@ public void onAdEvent(AdEvent adEvent) { @Override public void onAdError(AdErrorEvent adErrorEvent) { - eventEmitter.receiveAdErrorEvent(adErrorEvent.getError()); +// todo +// eventEmitter.receiveAdErrorEvent(adErrorEvent.getError()); } } diff --git a/android/src/main/java/com/brentvatne/react/ReactVideoPackage.java b/android/src/main/java/com/brentvatne/react/ReactVideoPackage.java index 6f93485889..1539cba542 100644 --- a/android/src/main/java/com/brentvatne/react/ReactVideoPackage.java +++ b/android/src/main/java/com/brentvatne/react/ReactVideoPackage.java @@ -1,14 +1,15 @@ 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 com.brentvatne.exoplayer.ReactExoplayerConfig; +import com.facebook.react.bridge.JavaScriptModule; + import java.util.ArrayList; import java.util.Collections; import java.util.List; diff --git a/android/src/main/java/com/brentvatne/exoplayer/ReactExoplayerViewManager.java b/android/src/oldarch/java/com/brentvatne/exoplayer/ReactExoplayerViewManager.java similarity index 85% rename from android/src/main/java/com/brentvatne/exoplayer/ReactExoplayerViewManager.java rename to android/src/oldarch/java/com/brentvatne/exoplayer/ReactExoplayerViewManager.java index 6a0b198f4e..e3159eba38 100644 --- a/android/src/main/java/com/brentvatne/exoplayer/ReactExoplayerViewManager.java +++ b/android/src/oldarch/java/com/brentvatne/exoplayer/ReactExoplayerViewManager.java @@ -6,6 +6,8 @@ import android.text.TextUtils; import android.util.Log; +import androidx.annotation.NonNull; + import androidx.media3.common.util.Util; import androidx.media3.datasource.RawResourceDataSource; import androidx.media3.exoplayer.DefaultLoadControl; @@ -33,26 +35,25 @@ public class ReactExoplayerViewManager extends ViewGroupManager { - private static final String REACT_CLASS = "RCTVideo"; + private static final String REACT_CLASS = "RNCVideo"; private static final String PROP_SRC = "src"; private static final String PROP_SRC_URI = "uri"; - private static final String PROP_SRC_START_POSITION = "startPosition"; private static final String PROP_SRC_CROP_START = "cropStart"; private static final String PROP_SRC_CROP_END = "cropEnd"; private static final String PROP_AD_TAG_URL = "adTagUrl"; private static final String PROP_SRC_TYPE = "type"; private static final String PROP_DRM = "drm"; - private static final String PROP_DRM_TYPE = "type"; + private static final String PROP_DRM_TYPE = "drmType"; private static final String PROP_DRM_LICENSESERVER = "licenseServer"; private static final String PROP_DRM_HEADERS = "headers"; private static final String PROP_SRC_HEADERS = "requestHeaders"; private static final String PROP_RESIZE_MODE = "resizeMode"; private static final String PROP_REPEAT = "repeat"; private static final String PROP_SELECTED_AUDIO_TRACK = "selectedAudioTrack"; - private static final String PROP_SELECTED_AUDIO_TRACK_TYPE = "type"; + private static final String PROP_SELECTED_AUDIO_TRACK_TYPE = "selectedAudioType"; private static final String PROP_SELECTED_AUDIO_TRACK_VALUE = "value"; private static final String PROP_SELECTED_TEXT_TRACK = "selectedTextTrack"; - private static final String PROP_SELECTED_TEXT_TRACK_TYPE = "type"; + private static final String PROP_SELECTED_TEXT_TRACK_TYPE = "selectedTextType"; private static final String PROP_SELECTED_TEXT_TRACK_VALUE = "value"; private static final String PROP_TEXT_TRACKS = "textTracks"; private static final String PROP_PAUSED = "paused"; @@ -129,18 +130,19 @@ public void setDRM(final ReactExoplayerView videoView, @Nullable ReadableMap drm if (drm != null && drm.hasKey(PROP_DRM_TYPE)) { String drmType = ReactBridgeUtils.safeGetString(drm, PROP_DRM_TYPE); String drmLicenseServer = ReactBridgeUtils.safeGetString(drm, PROP_DRM_LICENSESERVER); - ReadableMap drmHeaders = ReactBridgeUtils.safeGetMap(drm, PROP_DRM_HEADERS); + ReadableArray drmHeadersArray = (drm.hasKey(PROP_DRM_HEADERS)) ? drm.getArray(PROP_DRM_HEADERS) : null; if (drmType != null && drmLicenseServer != null && Util.getDrmUuid(drmType) != null) { UUID drmUUID = Util.getDrmUuid(drmType); videoView.setDrmType(drmUUID); videoView.setDrmLicenseUrl(drmLicenseServer); - if (drmHeaders != null) { + if (drmHeadersArray != null) { ArrayList drmKeyRequestPropertiesList = new ArrayList<>(); - ReadableMapKeySetIterator itr = drmHeaders.keySetIterator(); - while (itr.hasNextKey()) { - String key = itr.nextKey(); + for (int i = 0; i < drmHeadersArray.size(); i++) { + ReadableMap current = drmHeadersArray.getMap(i); + String key = current.hasKey("key") ? current.getString("key") : null; + String value = current.hasKey("value") ? current.getString("value") : null; drmKeyRequestPropertiesList.add(key); - drmKeyRequestPropertiesList.add(drmHeaders.getString(key)); + drmKeyRequestPropertiesList.add(value); } videoView.setDrmLicenseHeader(drmKeyRequestPropertiesList.toArray(new String[0])); } @@ -152,14 +154,26 @@ public void setDRM(final ReactExoplayerView videoView, @Nullable ReadableMap drm @ReactProp(name = PROP_SRC) public void setSrc(final ReactExoplayerView videoView, @Nullable ReadableMap src) { Context context = videoView.getContext().getApplicationContext(); + Map headers = new HashMap<>(); + ReadableArray propSrcHeadersArray = (src.hasKey(PROP_SRC_HEADERS)) ? src.getArray(PROP_SRC_HEADERS) : null; + if (propSrcHeadersArray != null) { + if (propSrcHeadersArray.size() > 0) { + for (int i = 0; i < propSrcHeadersArray.size(); i++) { + ReadableMap current = propSrcHeadersArray.getMap(i); + String key = current.hasKey("key") ? current.getString("key") : null; + String value = current.hasKey("value") ? current.getString("value") : null; + if (key != null && value != null) { + headers.put(key, value); + } + } + } + } String uriString = ReactBridgeUtils.safeGetString(src, PROP_SRC_URI, null); int startPositionMs = ReactBridgeUtils.safeGetInt(src, PROP_START_POSITION, -1); int cropStartMs = ReactBridgeUtils.safeGetInt(src, PROP_SRC_CROP_START, -1); int cropEndMs = ReactBridgeUtils.safeGetInt(src, PROP_SRC_CROP_END, -1); String extension = ReactBridgeUtils.safeGetString(src, PROP_SRC_TYPE, null); - Map headers = src.hasKey(PROP_SRC_HEADERS) ? ReactBridgeUtils.toStringMap(src.getMap(PROP_SRC_HEADERS)) : new HashMap<>(); - if (TextUtils.isEmpty(uriString)) { videoView.clearSrc(); return; @@ -314,6 +328,17 @@ public void setSeek(final ReactExoplayerView videoView, final float seek) { videoView.seekTo(Math.round(seek * 1000f)); } + @Override + public void receiveCommand(@NonNull ReactExoplayerView root, String commandId, @androidx.annotation.Nullable ReadableArray args) { + switch (commandId) { + case "seek": + this.setSeek(root, args.getInt(0)); + break; + default: + break; + } + } + @ReactProp(name = PROP_RATE) public void setRate(final ReactExoplayerView videoView, final float rate) { videoView.setRateModifier(rate); @@ -441,4 +466,43 @@ private boolean startsWithValidScheme(String uriString) { || lowerCaseUri.startsWith("file://") || lowerCaseUri.startsWith("asset://"); } + + private @ResizeMode.Mode int convertToIntDef(String resizeModeOrdinalString) { + if (!TextUtils.isEmpty(resizeModeOrdinalString)) { + if (resizeModeOrdinalString.equals("none")) { + return ResizeMode.toResizeMode(ResizeMode.RESIZE_MODE_FIT); + } else if (resizeModeOrdinalString.equals("contain")) { + return ResizeMode.toResizeMode(ResizeMode.RESIZE_MODE_FIT); + } else if (resizeModeOrdinalString.equals("cover")) { + return ResizeMode.toResizeMode(ResizeMode.RESIZE_MODE_CENTER_CROP); + } else if (resizeModeOrdinalString.equals("stretch")) { + return ResizeMode.toResizeMode(ResizeMode.RESIZE_MODE_FILL); + } + } + return ResizeMode.RESIZE_MODE_FIT; + } + + /** + * 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 toStringMap(@Nullable ReadableMap readableMap) { + if (readableMap == null) + return null; + + com.facebook.react.bridge.ReadableMapKeySetIterator iterator = readableMap.keySetIterator(); + if (!iterator.hasNextKey()) + return null; + + Map result = new HashMap<>(); + while (iterator.hasNextKey()) { + String key = iterator.nextKey(); + result.put(key, readableMap.getString(key)); + } + + return result; + } } diff --git a/android/src/main/java/com/brentvatne/common/react/VideoEventEmitter.java b/android/src/oldarch/java/com/brentvatne/exoplayer/VideoEventEmitter.java similarity index 98% rename from android/src/main/java/com/brentvatne/common/react/VideoEventEmitter.java rename to android/src/oldarch/java/com/brentvatne/exoplayer/VideoEventEmitter.java index 31cbc27f61..dec34af641 100644 --- a/android/src/main/java/com/brentvatne/common/react/VideoEventEmitter.java +++ b/android/src/oldarch/java/com/brentvatne/exoplayer/VideoEventEmitter.java @@ -133,6 +133,7 @@ public VideoEventEmitter(ReactContext reactContext) { private static final String EVENT_PROP_CURRENT_TIME = "currentTime"; private static final String EVENT_PROP_CURRENT_PLAYBACK_TIME = "currentPlaybackTime"; private static final String EVENT_PROP_SEEK_TIME = "seekTime"; + private static final String EVENT_PROP_FINISHED = "finished"; private static final String EVENT_PROP_NATURAL_SIZE = "naturalSize"; private static final String EVENT_PROP_TRACK_ID = "trackId"; private static final String EVENT_PROP_WIDTH = "width"; @@ -303,10 +304,11 @@ public void bandwidthReport(double bitRateEstimate, int height, int width, Strin receiveEvent(EVENT_BANDWIDTH, event); } - public void seek(long currentPosition, long seekTime) { + public void seek(long currentPosition, long seekTime, boolean finished) { WritableMap event = Arguments.createMap(); event.putDouble(EVENT_PROP_CURRENT_TIME, currentPosition / 1000D); event.putDouble(EVENT_PROP_SEEK_TIME, seekTime / 1000D); + event.putBoolean(EVENT_PROP_FINISHED, finished); receiveEvent(EVENT_SEEK, event); } @@ -451,4 +453,4 @@ public void receiveAdErrorEvent(AdError error) { private void receiveEvent(@VideoEvents String type, WritableMap event) { eventEmitter.receiveEvent(viewId, type, event); } -} +} \ No newline at end of file diff --git a/examples/FabricExample/android/app/build.gradle b/examples/FabricExample/android/app/build.gradle index 1266df3d88..58058009cf 100644 --- a/examples/FabricExample/android/app/build.gradle +++ b/examples/FabricExample/android/app/build.gradle @@ -165,6 +165,15 @@ dependencies { } else { implementation jscFlavor } + + constraints { + implementation("org.jetbrains.kotlin:kotlin-stdlib-jdk7:1.8.0") { + because("kotlin-stdlib-jdk7 is now a part of kotlin-stdlib") + } + implementation("org.jetbrains.kotlin:kotlin-stdlib-jdk8:1.8.0") { + because("kotlin-stdlib-jdk8 is now a part of kotlin-stdlib") + } + } } apply from: file("../../node_modules/@react-native-community/cli-platform-android/native_modules.gradle"); applyNativeModulesAppBuildGradle(project) \ No newline at end of file diff --git a/examples/FabricExample/android/build.gradle b/examples/FabricExample/android/build.gradle index c30aa72af7..fe28955f1f 100644 --- a/examples/FabricExample/android/build.gradle +++ b/examples/FabricExample/android/build.gradle @@ -3,9 +3,9 @@ buildscript { ext { buildToolsVersion = "31.0.0" - kotlinVersion = "1.6.20" + kotlinVersion = "1.8.0" minSdkVersion = 21 - compileSdkVersion = 31 + compileSdkVersion = 33 targetSdkVersion = 31 // We use NDK 23 which has both M1 support and is the side-by-side NDK version from AGP. diff --git a/examples/FabricExample/ios/Podfile.lock b/examples/FabricExample/ios/Podfile.lock index 2fc3f61321..d3fbc17c17 100644 --- a/examples/FabricExample/ios/Podfile.lock +++ b/examples/FabricExample/ios/Podfile.lock @@ -659,10 +659,10 @@ PODS: - React-jsinspector (0.71.12) - React-logger (0.71.12): - glog - - react-native-video (6.0.0-alpha.6): + - react-native-video (7.0.0-alpha.19): - React-Core - - react-native-video/Video (= 6.0.0-alpha.6) - - react-native-video/Video (6.0.0-alpha.6): + - react-native-video/Video (= 7.0.0-alpha.19) + - react-native-video/Video (7.0.0-alpha.19): - PromisesSwift - React-Core - React-perflogger (0.71.12) @@ -967,7 +967,7 @@ SPEC CHECKSUMS: React-jsiexecutor: 509cd947c28834614808ce056ee8eb700a0662aa React-jsinspector: ec4dcbfb1f4e72f04f826a0301eceee5fa7ca540 React-logger: 35538accacf2583693fbc3ee8b53e69a1776fcee - react-native-video: fee89269ad07556d960721f3b62e39be6ace3c90 + react-native-video: 6c3b31628131f27ed5d423aa45193de6756ecdfa React-perflogger: 75b0e25075c67565a830985f3c373e2eae5389e0 React-RCTActionSheet: a0c3e916b327e297d124d9ebe8b0c721840ee04d React-RCTAnimation: 3da7025801d7bf0f8cfd94574d6278d5b82a8b88 diff --git a/examples/FabricExample/tsconfig.json b/examples/FabricExample/tsconfig.json index 6c939fc72a..8d8813b5a0 100644 --- a/examples/FabricExample/tsconfig.json +++ b/examples/FabricExample/tsconfig.json @@ -4,7 +4,7 @@ "compilerOptions": { /* Visit https://aka.ms/tsconfig.json to read more about this file */ "paths": { - "react-native-video": ["../../Video.js"] + "react-native-video": ["../../src/index.ts"] }, /* Completeness */ "skipLibCheck": true /* Skip type checking all .d.ts files. */ diff --git a/examples/basic/metro.config.js b/examples/basic/metro.config.js index b09acb86c4..46475fb308 100644 --- a/examples/basic/metro.config.js +++ b/examples/basic/metro.config.js @@ -1,57 +1,40 @@ -/** - * Metro configuration for React Native - * https://github.com/facebook/react-native - * - * @format - */ const path = require('path'); const escape = require('escape-string-regexp'); +const exclusionList = require('metro-config/src/defaults/exclusionList'); +const pak = require('../../package.json'); -const blacklist = require('metro-config/src/defaults/exclusionList'); -const {getDefaultConfig, mergeConfig} = require('@react-native/metro-config'); +const root = path.resolve(__dirname, '../../'); -const pak = require('../../package.json'); -const root = path.resolve(__dirname, '../..'); -const modules = Object.keys({...pak.peerDependencies}); +const modules = Object.keys({ + ...pak.peerDependencies, +}); -/** - * Metro configuration - * https://facebook.github.io/metro/docs/configuration - * - * @type {import('metro-config').MetroConfig} - */ -const config = { +module.exports = { + projectRoot: __dirname, watchFolders: [root], + + // We need to make sure that only one version is loaded for peerDependencies + // So we block them at the root, and alias them to the versions in example's node_modules resolver: { - blacklistRE: blacklist([ - // This stops "react-native run-windows" from causing the metro server to crash if its already running - new RegExp( - `${path.resolve(__dirname, 'windows').replace(/[/\\]/g, '/')}.*`, - ), - // This prevents "react-native run-windows" from hitting: EBUSY: resource busy or locked, open msbuild.ProjectImports.zip - /.*\.ProjectImports\.zip/, - /(.*\/react-native-video\/node_modules\/.*)$/, + blacklistRE: exclusionList( + modules.map( + (m) => + new RegExp(`^${escape(path.join(root, 'node_modules', m))}\\/.*$`) + ) + ), - // We need to make sure that only one version is loaded for peerDependencies - // So we block them at the root, and alias them to the versions in example's node_modules - ...modules.map( - name => - new RegExp(`^${escape(path.join(root, 'node_modules', name))}\\/.*$`), - ), - ]), extraNodeModules: modules.reduce((acc, name) => { acc[name] = path.join(__dirname, 'node_modules', name); return acc; }, {}), - transformer: { - getTransformOptions: async () => ({ - transform: { - experimentalImportSupport: false, - inlineRequires: true, - }, - }), - }, }, -}; -module.exports = mergeConfig(getDefaultConfig(__dirname), config); + transformer: { + getTransformOptions: async () => ({ + transform: { + experimentalImportSupport: false, + inlineRequires: true, + }, + }), + }, +}; diff --git a/examples/basic/tsconfig.json b/examples/basic/tsconfig.json index a2371834a5..1d57b91b3c 100644 --- a/examples/basic/tsconfig.json +++ b/examples/basic/tsconfig.json @@ -2,7 +2,7 @@ "extends": "@react-native/typescript-config/tsconfig.json", "compilerOptions": { "paths": { - "react-native-video": ["../../src/index"] + "react-native-video": ["../../src/index.ts"] } }, "include": ["src", "**/*.js"], diff --git a/ios/Video/RCTVideoManager.swift b/ios/Video/RCTVideoManager.swift index bfc398d7b4..dd11bb7354 100644 --- a/ios/Video/RCTVideoManager.swift +++ b/ios/Video/RCTVideoManager.swift @@ -22,7 +22,6 @@ class RCTVideoManager: RCTViewManager { } } } - @objc(setLicenseResult:licenseUrl:reactTag:) func setLicenseResult(license: NSString, licenseUrl: NSString, reactTag: NSNumber) { bridge.uiManager.prependUIBlock { _, viewRegistry in diff --git a/package.json b/package.json index 2c3cdb524b..733f4b8640 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "react-native-video", - "version": "6.0.0-beta.3", + "version": "7.0.0-alpha.19", "description": "A