diff --git a/android/src/main/java/com/brentvatne/common/api/Source.kt b/android/src/main/java/com/brentvatne/common/api/Source.kt new file mode 100644 index 00000000..60c2711b --- /dev/null +++ b/android/src/main/java/com/brentvatne/common/api/Source.kt @@ -0,0 +1,210 @@ +package com.brentvatne.common.api + +import android.annotation.SuppressLint +import android.content.ContentResolver +import android.content.Context +import android.content.res.Resources +import android.net.Uri +import android.text.TextUtils +import com.brentvatne.common.toolbox.DebugLog +import com.brentvatne.common.toolbox.DebugLog.e +import com.brentvatne.common.toolbox.ReactBridgeUtils.safeGetArray +import com.brentvatne.common.toolbox.ReactBridgeUtils.safeGetInt +import com.brentvatne.common.toolbox.ReactBridgeUtils.safeGetMap +import com.brentvatne.common.toolbox.ReactBridgeUtils.safeGetString +import com.facebook.react.bridge.ReadableMap +import java.util.Locale + +/** + * Class representing Source props for host. + * Only generic code here, no reference to the player. + */ +class Source { + /** String value of source to playback */ + private var uriString: String? = null + + /** Parsed value of source to playback */ + var uri: Uri? = null + + /** Start position of playback used to resume playback */ + var startPositionMs: Int = -1 + + /** Will crop content start at specified position */ + var cropStartMs: Int = -1 + + /** Will crop content end at specified position */ + var cropEndMs: Int = -1 + + /** Allow to force stream content, necessary when uri doesn't contain content type (.mlp4, .m3u, ...) */ + var extension: String? = null + + /** Metadata to display in notification */ + var metadata: Metadata? = null + + /** http header list */ + val headers: MutableMap = HashMap() + + /** return true if this and src are equals */ + override fun equals(other: Any?): Boolean { + if (other == null || other !is Source) return false + return ( + uri == other.uri && + cropStartMs == other.cropStartMs && + cropEndMs == other.cropEndMs && + startPositionMs == other.startPositionMs && + extension == other.extension + ) + } + + /** return true if this and src are equals */ + fun isEquals(source: Source): Boolean { + return this == source + } + + /** Metadata to display in notification */ + class Metadata { + /** Metadata title */ + var title: String? = null + + /** Metadata subtitle */ + var subtitle: String? = null + + /** Metadata description */ + var description: String? = null + + /** Metadata artist */ + var artist: String? = null + + /** image uri to display */ + var imageUri: Uri? = null + + companion object { + private const val PROP_SRC_METADATA_TITLE = "title" + private const val PROP_SRC_METADATA_SUBTITLE = "subtitle" + private const val PROP_SRC_METADATA_DESCRIPTION = "description" + private const val PROP_SRC_METADATA_ARTIST = "artist" + private const val PROP_SRC_METADATA_IMAGE_URI = "imageUri" + + /** parse metadata object */ + @JvmStatic + fun parse(src: ReadableMap?): Metadata? { + if (src != null) { + val metadata = Metadata() + metadata.title = safeGetString(src, PROP_SRC_METADATA_TITLE) + metadata.subtitle = safeGetString(src, PROP_SRC_METADATA_SUBTITLE) + metadata.description = safeGetString(src, PROP_SRC_METADATA_DESCRIPTION) + metadata.artist = safeGetString(src, PROP_SRC_METADATA_ARTIST) + val imageUriString = safeGetString(src, PROP_SRC_METADATA_IMAGE_URI) + try { + metadata.imageUri = Uri.parse(imageUriString) + } catch (e: Exception) { + e(TAG, "Could not parse imageUri in metadata") + } + return metadata + } + return null + } + } + } + + companion object { + private const val TAG = "Source" + private const val PROP_SRC_URI = "uri" + private const val PROP_SRC_START_POSITION = "startPosition" + private const val PROP_SRC_CROP_START = "cropStart" + private const val PROP_SRC_CROP_END = "cropEnd" + private const val PROP_SRC_TYPE = "type" + private const val PROP_SRC_METADATA = "metadata" + private const val PROP_SRC_HEADERS = "requestHeaders" + + @SuppressLint("DiscouragedApi") + private fun getUriFromAssetId(context: Context, uriString: String): Uri? { + val resources: Resources = context.resources + val packageName: String = context.packageName + var identifier = resources.getIdentifier( + uriString, + "drawable", + packageName + ) + if (identifier == 0) { + identifier = resources.getIdentifier( + uriString, + "raw", + packageName + ) + } + + if (identifier <= 0) { + // cannot find identifier of content + DebugLog.d(TAG, "cannot find identifier") + return null + } + return Uri.Builder().scheme(ContentResolver.SCHEME_ANDROID_RESOURCE).path(identifier.toString()).build() + } + + /** parse the source ReadableMap received from app */ + @JvmStatic + fun parse(src: ReadableMap?, context: Context): Source { + val source = Source() + + if (src != null) { + val uriString = safeGetString(src, PROP_SRC_URI, null) + if (uriString == null || TextUtils.isEmpty(uriString)) { + DebugLog.d(TAG, "isEmpty uri:$uriString") + return source + } + var uri = Uri.parse(uriString) + if (uri == null) { + // return an empty source + DebugLog.d(TAG, "Invalid uri:$uriString") + return source + } else if (!isValidScheme(uri.scheme)) { + uri = getUriFromAssetId(context, uriString) + if (uri == null) { + // cannot find identifier of content + DebugLog.d(TAG, "cannot find identifier") + return source + } + } + source.uriString = uriString + source.uri = uri + source.startPositionMs = safeGetInt(src, PROP_SRC_START_POSITION, -1) + source.cropStartMs = safeGetInt(src, PROP_SRC_CROP_START, -1) + source.cropEndMs = safeGetInt(src, PROP_SRC_CROP_END, -1) + source.extension = safeGetString(src, PROP_SRC_TYPE, null) + + val propSrcHeadersArray = safeGetArray(src, PROP_SRC_HEADERS) + if (propSrcHeadersArray != null) { + if (propSrcHeadersArray.size() > 0) { + for (i in 0 until propSrcHeadersArray.size()) { + val current = propSrcHeadersArray.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) { + source.headers[key] = value + } + } + } + } + source.metadata = Metadata.parse(safeGetMap(src, PROP_SRC_METADATA)) + } + return source + } + + /** return true if rui scheme is supported for android playback */ + private fun isValidScheme(scheme: String?): Boolean { + if (scheme == null) { + return false + } + val lowerCaseUri = scheme.lowercase(Locale.getDefault()) + return ( + lowerCaseUri == "http" || + lowerCaseUri == "https" || + lowerCaseUri == "content" || + lowerCaseUri == "file" || + lowerCaseUri == "rtsp" || + lowerCaseUri == "asset" + ) + } + } +} diff --git a/android/src/main/java/com/brentvatne/exoplayer/ConfigurationUtils.kt b/android/src/main/java/com/brentvatne/exoplayer/ConfigurationUtils.kt index 6900a037..440e8fce 100644 --- a/android/src/main/java/com/brentvatne/exoplayer/ConfigurationUtils.kt +++ b/android/src/main/java/com/brentvatne/exoplayer/ConfigurationUtils.kt @@ -1,8 +1,10 @@ package com.brentvatne.exoplayer import androidx.media3.common.MediaItem.LiveConfiguration +import androidx.media3.common.MediaMetadata import com.brentvatne.common.api.BufferConfig import com.brentvatne.common.api.BufferConfig.Live +import com.brentvatne.common.api.Source /** * Helper functions to create exoplayer configuration @@ -33,4 +35,22 @@ object ConfigurationUtils { } return liveConfiguration } + + /** + * Generate exoplayer MediaMetadata from source.Metadata + */ + @JvmStatic + fun buildCustomMetadata(metadata: Source.Metadata?): MediaMetadata? { + var customMetadata: MediaMetadata? = null + if (metadata != null) { + customMetadata = MediaMetadata.Builder() + .setTitle(metadata.title) + .setSubtitle(metadata.subtitle) + .setDescription(metadata.description) + .setArtist(metadata.artist) + .setArtworkUri(metadata.imageUri) + .build() + } + return customMetadata + } } diff --git a/android/src/main/java/com/brentvatne/exoplayer/ReactExoplayerView.java b/android/src/main/java/com/brentvatne/exoplayer/ReactExoplayerView.java index 957f7c28..9ff301e1 100644 --- a/android/src/main/java/com/brentvatne/exoplayer/ReactExoplayerView.java +++ b/android/src/main/java/com/brentvatne/exoplayer/ReactExoplayerView.java @@ -113,6 +113,7 @@ import com.brentvatne.common.api.ControlsConfig; import com.brentvatne.common.api.ResizeMode; import com.brentvatne.common.api.SideLoadedTextTrack; import com.brentvatne.common.api.SideLoadedTextTrackList; +import com.brentvatne.common.api.Source; import com.brentvatne.common.api.SubtitleStyle; import com.brentvatne.common.api.TimedMetadata; import com.brentvatne.common.api.Track; @@ -137,7 +138,6 @@ import java.net.CookieManager; import java.net.CookiePolicy; import java.lang.Math; import java.util.List; -import java.util.Map; import java.util.ArrayList; import java.util.Locale; import java.util.Objects; @@ -220,11 +220,7 @@ public class ReactExoplayerView extends FrameLayout implements private ControlsConfig controlsConfig = new ControlsConfig(); // Props from React - private Uri srcUri; - private long startPositionMs = -1; - private long cropStartMs = -1; - private long cropEndMs = -1; - private String extension; + private Source source = new Source(); private boolean repeat; private String audioTrackType; private String audioTrackValue; @@ -241,7 +237,6 @@ public class ReactExoplayerView extends FrameLayout implements private boolean preventsDisplaySleepDuringVideoPlayback = true; private float mProgressUpdateInterval = 250.0f; private boolean playInBackground = false; - private Map requestHeaders; private boolean mReportBandwidth = false; private UUID drmUUID = null; private String drmLicenseUrl = null; @@ -324,8 +319,6 @@ public class ReactExoplayerView extends FrameLayout implements } private void createViews() { - clearResumePosition(); - mediaDataSourceFactory = buildDataSourceFactory(true); if (CookieHandler.getDefault() != DEFAULT_COOKIE_MANAGER) { CookieHandler.setDefault(DEFAULT_COOKIE_MANAGER); } @@ -652,22 +645,15 @@ public class ReactExoplayerView extends FrameLayout implements // Initialize core configuration and listeners initializePlayerCore(self); } - if (playerNeedsSource && srcUri != null) { + if (playerNeedsSource && source.getUri() != null) { exoPlayerView.invalidateAspectRatio(); // DRM session manager creation must be done on a different thread to prevent crashes so we start a new thread ExecutorService es = Executors.newSingleThreadExecutor(); es.execute(() -> { // DRM initialization must run on a different thread - DrmSessionManager drmSessionManager = initializePlayerDrm(self); - if (drmSessionManager == null && self.drmUUID != null) { - // Failed to intialize DRM session manager - cannot continue - DebugLog.e(TAG, "Failed to initialize DRM Session Manager Framework!"); - eventEmitter.error("Failed to initialize DRM Session Manager Framework!", new Exception("DRM Session Manager Framework failure!"), "3003"); - return; - } if (activity == null) { - DebugLog.e(TAG, "Failed to initialize Player!"); + DebugLog.e(TAG, "Failed to initialize Player!, null activity"); eventEmitter.error("Failed to initialize Player!", new Exception("Current Activity is null!"), "1001"); return; } @@ -676,22 +662,24 @@ public class ReactExoplayerView extends FrameLayout implements activity.runOnUiThread(() -> { try { // Source initialization must run on the main thread - initializePlayerSource(self, drmSessionManager); + initializePlayerSource(); } catch (Exception ex) { self.playerNeedsSource = true; DebugLog.e(TAG, "Failed to initialize Player!"); DebugLog.e(TAG, ex.toString()); + ex.printStackTrace(); self.eventEmitter.error(ex.toString(), ex, "1001"); } }); }); - } else if (srcUri != null) { - initializePlayerSource(self, null); + } else if (source.getUri() != null) { + initializePlayerSource(); } } catch (Exception ex) { self.playerNeedsSource = true; DebugLog.e(TAG, "Failed to initialize Player!"); DebugLog.e(TAG, ex.toString()); + ex.printStackTrace(); eventEmitter.error(ex.toString(), ex, "1001"); } }; @@ -782,15 +770,26 @@ public class ReactExoplayerView extends FrameLayout implements return drmSessionManager; } - private void initializePlayerSource(ReactExoplayerView self, DrmSessionManager drmSessionManager) { + private void initializePlayerSource() { + if (source.getUri() == null) { + return; + } + DrmSessionManager drmSessionManager = initializePlayerDrm(this); + if (drmSessionManager == null && drmUUID != null) { + // Failed to intialize DRM session manager - cannot continue + DebugLog.e(TAG, "Failed to initialize DRM Session Manager Framework!"); + eventEmitter.error("Failed to initialize DRM Session Manager Framework!", new Exception("DRM Session Manager Framework failure!"), "3003"); + return; + } + ArrayList mediaSourceList = buildTextSources(); - MediaSource videoSource = buildMediaSource(self.srcUri, self.extension, drmSessionManager, cropStartMs, cropEndMs); + MediaSource videoSource = buildMediaSource(source.getUri(), source.getExtension(), drmSessionManager, source.getCropStartMs(), source.getCropEndMs()); MediaSource mediaSourceWithAds = null; if (adTagUrl != null && adsLoader != null) { DefaultMediaSourceFactory mediaSourceFactory = new DefaultMediaSourceFactory(mediaDataSourceFactory) .setLocalAdInsertionComponents(unusedAdTagUri -> adsLoader, exoPlayerView); DataSpec adTagDataSpec = new DataSpec(adTagUrl); - mediaSourceWithAds = new AdsMediaSource(videoSource, adTagDataSpec, ImmutableList.of(srcUri, adTagUrl), mediaSourceFactory, adsLoader, exoPlayerView); + mediaSourceWithAds = new AdsMediaSource(videoSource, adTagDataSpec, ImmutableList.of(source.getUri(), adTagUrl), mediaSourceFactory, adsLoader, exoPlayerView); } else { if (adTagUrl == null && adsLoader != null) { adsLoader.release(); @@ -830,8 +829,8 @@ public class ReactExoplayerView extends FrameLayout implements if (haveResumePosition) { player.seekTo(resumeWindow, resumePosition); player.setMediaSource(mediaSource, false); - } else if (startPositionMs > 0) { - player.setMediaSource(mediaSource, startPositionMs); + } else if (source.getStartPositionMs() > 0) { + player.setMediaSource(mediaSource, source.getStartPositionMs()); } else { player.setMediaSource(mediaSource, true); } @@ -1029,14 +1028,14 @@ public class ReactExoplayerView extends FrameLayout implements ); break; case CONTENT_TYPE_OTHER: - if ("asset".equals(srcUri.getScheme())) { + if ("asset".equals(uri.getScheme())) { try { - DataSource.Factory assetDataSourceFactory = buildAssetDataSourceFactory(themedReactContext, srcUri); + DataSource.Factory assetDataSourceFactory = buildAssetDataSourceFactory(themedReactContext, uri); mediaSourceFactory = new ProgressiveMediaSource.Factory(assetDataSourceFactory); } catch (Exception e) { - throw new IllegalStateException("cannot open input file" + srcUri); + throw new IllegalStateException("cannot open input file" + uri); } - } else if ("file".equals(srcUri.getScheme()) || + } else if ("file".equals(uri.getScheme()) || !useCache) { mediaSourceFactory = new ProgressiveMediaSource.Factory( mediaDataSourceFactory @@ -1193,7 +1192,7 @@ public class ReactExoplayerView extends FrameLayout implements } private boolean requestAudioFocus() { - if (disableFocus || srcUri == null || this.hasAudioFocus) { + if (disableFocus || source.getUri() == null || this.hasAudioFocus) { return true; } int result = audioManager.requestAudioFocus(audioFocusChangeListener, @@ -1270,7 +1269,7 @@ public class ReactExoplayerView extends FrameLayout implements */ private DataSource.Factory buildDataSourceFactory(boolean useBandwidthMeter) { return DataSourceUtil.getDefaultDataSourceFactory(this.themedReactContext, - useBandwidthMeter ? bandwidthMeter : null, requestHeaders); + useBandwidthMeter ? bandwidthMeter : null, source.getHeaders()); } /** @@ -1281,7 +1280,7 @@ public class ReactExoplayerView extends FrameLayout implements * @return A new HttpDataSource factory. */ private HttpDataSource.Factory buildHttpDataSourceFactory(boolean useBandwidthMeter) { - return DataSourceUtil.getDefaultHttpDataSourceFactory(this.themedReactContext, useBandwidthMeter ? bandwidthMeter : null, requestHeaders); + return DataSourceUtil.getDefaultHttpDataSourceFactory(this.themedReactContext, useBandwidthMeter ? bandwidthMeter : null, source.getHeaders()); } // AudioBecomingNoisyListener implementation @@ -1486,7 +1485,7 @@ public class ReactExoplayerView extends FrameLayout implements private ArrayList getVideoTrackInfoFromManifest(int retryCount) { ExecutorService es = Executors.newSingleThreadExecutor(); final DataSource dataSource = this.mediaDataSourceFactory.createDataSource(); - final Uri sourceUri = this.srcUri; + final Uri sourceUri = source.getUri(); final long startTime = this.contentStartTime * 1000 - 100; // s -> ms with 100ms offset Future> result = es.submit(new Callable() { @@ -1729,34 +1728,29 @@ public class ReactExoplayerView extends FrameLayout implements // ReactExoplayerViewManager public api - public void setSrc(final Uri uri, final long startPositionMs, final long cropStartMs, final long cropEndMs, final String extension, Map headers, MediaMetadata customMetadata) { - - if (!Util.areEqual(this.customMetadata, customMetadata) && player != null) { - MediaItem currentMediaItem = player.getCurrentMediaItem(); - - if (currentMediaItem != null) { - - MediaItem newMediaItem = currentMediaItem.buildUpon().setMediaMetadata(customMetadata).build(); - - // This will cause video blink/reload but won't louse progress - player.setMediaItem(newMediaItem, false); - } - } - - if (uri != null) { - boolean isSourceEqual = uri.equals(srcUri) && cropStartMs == this.cropStartMs && cropEndMs == this.cropEndMs; + public void setSrc(Source source) { + if (source.getUri() != null) { + clearResumePosition(); + boolean isSourceEqual = source.isEquals(this.source); hasDrmFailed = false; - this.srcUri = uri; - this.startPositionMs = startPositionMs; - this.cropStartMs = cropStartMs; - this.cropEndMs = cropEndMs; - this.extension = extension; - this.requestHeaders = headers; + this.source = source; this.mediaDataSourceFactory = DataSourceUtil.getDefaultDataSourceFactory(this.themedReactContext, bandwidthMeter, - this.requestHeaders); - this.customMetadata = customMetadata; + source.getHeaders()); + // refresh custom Metadata + MediaMetadata newCustomMetadata = ConfigurationUtils.buildCustomMetadata(source.getMetadata()); + + // Apply custom metadata is possible + if (player != null && !Util.areEqual(newCustomMetadata, customMetadata)) { + customMetadata = newCustomMetadata; + MediaItem currentMediaItem = player.getCurrentMediaItem(); + if (currentMediaItem != null) { + MediaItem newMediaItem = currentMediaItem.buildUpon().setMediaMetadata(customMetadata).build(); + // This will cause video blink/reload but won't louse progress + player.setMediaItem(newMediaItem, false); + } + } if (!isSourceEqual) { reloadSource(); } @@ -1764,19 +1758,13 @@ public class ReactExoplayerView extends FrameLayout implements } public void clearSrc() { - if (srcUri != null) { + if (source.getUri() != null) { if (player != null) { player.stop(); player.clearMediaItems(); } - this.srcUri = null; - this.startPositionMs = -1; - this.cropStartMs = -1; - this.cropEndMs = -1; - this.extension = null; - this.requestHeaders = null; + this.source = new Source(); this.mediaDataSourceFactory = null; - customMetadata = null; clearResumePosition(); } } @@ -1793,22 +1781,9 @@ public class ReactExoplayerView extends FrameLayout implements adTagUrl = uri; } - public void setRawSrc(final Uri uri, final String extension) { - if (uri != null) { - boolean isSourceEqual = uri.equals(srcUri); - this.srcUri = uri; - this.extension = extension; - this.mediaDataSourceFactory = buildDataSourceFactory(true); - - if (!isSourceEqual) { - reloadSource(); - } - } - } - public void setTextTracks(SideLoadedTextTrackList textTracks) { this.textTracks = textTracks; - reloadSource(); + reloadSource(); // FIXME Shall be moved inside source } private void reloadSource() { diff --git a/android/src/main/java/com/brentvatne/exoplayer/ReactExoplayerViewManager.java b/android/src/main/java/com/brentvatne/exoplayer/ReactExoplayerViewManager.java index d92b15a8..8c34a2ac 100644 --- a/android/src/main/java/com/brentvatne/exoplayer/ReactExoplayerViewManager.java +++ b/android/src/main/java/com/brentvatne/exoplayer/ReactExoplayerViewManager.java @@ -1,23 +1,20 @@ package com.brentvatne.exoplayer; -import android.content.ContentResolver; import android.content.Context; -import android.content.res.Resources; import android.graphics.Color; import android.net.Uri; import android.text.TextUtils; import android.util.Log; import androidx.annotation.NonNull; -import androidx.media3.common.MediaMetadata; import androidx.media3.common.util.Util; -import androidx.media3.datasource.RawResourceDataSource; import com.brentvatne.common.api.BufferConfig; import com.brentvatne.common.api.BufferingStrategy; import com.brentvatne.common.api.ControlsConfig; import com.brentvatne.common.api.ResizeMode; import com.brentvatne.common.api.SideLoadedTextTrackList; +import com.brentvatne.common.api.Source; import com.brentvatne.common.api.SubtitleStyle; import com.brentvatne.common.react.VideoEventEmitter; import com.brentvatne.common.toolbox.DebugLog; @@ -29,7 +26,6 @@ import com.facebook.react.uimanager.ThemedReactContext; import com.facebook.react.uimanager.ViewGroupManager; import com.facebook.react.uimanager.annotations.ReactProp; -import java.util.HashMap; import java.util.ArrayList; import java.util.Map; import java.util.UUID; @@ -41,18 +37,11 @@ public class ReactExoplayerViewManager extends ViewGroupManager headers = new HashMap<>(); - ReadableArray propSrcHeadersArray = ReactBridgeUtils.safeGetArray(src, PROP_SRC_HEADERS); - 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); - } - } - } - } - - ReadableMap propMetadata = ReactBridgeUtils.safeGetMap(src, PROP_SRC_METADATA); - MediaMetadata customMetadata = null; - if (propMetadata != null) { - String title = ReactBridgeUtils.safeGetString(propMetadata, "title"); - String subtitle = ReactBridgeUtils.safeGetString(propMetadata, "subtitle"); - String description = ReactBridgeUtils.safeGetString(propMetadata, "description"); - String artist = ReactBridgeUtils.safeGetString(propMetadata, "artist"); - String imageUriString = ReactBridgeUtils.safeGetString(propMetadata, "imageUri"); - - Uri imageUri = null; - - try { - imageUri = Uri.parse(imageUriString); - } catch (Exception e) { - DebugLog.e(TAG, "Could not parse imageUri in metadata"); - } - - customMetadata = new MediaMetadata.Builder() - .setTitle(title) - .setSubtitle(subtitle) - .setDescription(description) - .setArtist(artist) - .setArtworkUri(imageUri) - .build(); - } - - if (TextUtils.isEmpty(uriString)) { + Source source = Source.parse(src, context); + if (source.getUri() == null) { videoView.clearSrc(); - return; - } - - if (startsWithValidScheme(uriString)) { - Uri srcUri = Uri.parse(uriString); - - if (srcUri != null) { - videoView.setSrc(srcUri, startPositionMs, cropStartMs, cropEndMs, extension, headers, customMetadata); - } } else { - Resources resources = context.getResources(); - String packageName = context.getPackageName(); - int identifier = resources.getIdentifier( - uriString, - "drawable", - packageName - ); - if (identifier == 0) { - identifier = resources.getIdentifier( - uriString, - "raw", - packageName - ); - } - if (identifier > 0) { - Uri srcUri = new Uri.Builder().scheme(ContentResolver.SCHEME_ANDROID_RESOURCE).path(Integer.toString(identifier)).build(); - videoView.setRawSrc(srcUri, extension); - } else { - videoView.clearSrc(); - } + videoView.setSrc(source); } } @@ -462,14 +378,4 @@ public class ReactExoplayerViewManager extends ViewGroupManager