Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -24,13 +24,18 @@ import com.brentvatne.common.toolbox.DebugLog
import com.brentvatne.receiver.PictureInPictureReceiver
import com.facebook.react.uimanager.ThemedReactContext

internal fun Context.findActivity(): ComponentActivity {
internal fun Context.findActivity(): ComponentActivity? {
var context = this
while (context is ContextWrapper) {
if (context is ComponentActivity) return context
context = context.baseContext
}
throw IllegalStateException("Picture in picture should be called in the context of an Activity")
// Fallback: ThemedReactContext stores the activity separately from the ContextWrapper chain
if (this is ThemedReactContext) {
val activity = this.getCurrentActivity()
if (activity is ComponentActivity) return activity
}
return null
}

object PictureInPictureUtil {
Expand All @@ -39,7 +44,7 @@ object PictureInPictureUtil {

@JvmStatic
fun addLifecycleEventListener(context: ThemedReactContext, view: ReactExoplayerView): Runnable {
val activity = context.findActivity()
val activity = context.findActivity() ?: return Runnable {}

val onPictureInPictureModeChanged = Consumer<PictureInPictureModeChangedInfo> { info ->
view.setIsInPictureInPicture(info.isInPictureInPictureMode)
Expand Down Expand Up @@ -73,16 +78,17 @@ object PictureInPictureUtil {
@JvmStatic
fun enterPictureInPictureMode(context: ThemedReactContext, pictureInPictureParams: PictureInPictureParams?) {
if (!isSupportPictureInPicture(context)) return
val activity = context.findActivity() ?: return
if (isSupportPictureInPictureAction() && pictureInPictureParams != null) {
try {
context.findActivity().enterPictureInPictureMode(pictureInPictureParams)
activity.enterPictureInPictureMode(pictureInPictureParams)
} catch (e: IllegalStateException) {
DebugLog.e(TAG, e.toString())
}
} else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
try {
@Suppress("DEPRECATION")
context.findActivity().enterPictureInPictureMode()
activity.enterPictureInPictureMode()
} catch (e: IllegalStateException) {
DebugLog.e(TAG, e.toString())
}
Expand Down Expand Up @@ -119,8 +125,9 @@ object PictureInPictureUtil {
private fun updatePictureInPictureActions(context: ThemedReactContext, pipParams: PictureInPictureParams) {
if (!isSupportPictureInPictureAction()) return
if (!isSupportPictureInPicture(context)) return
val activity = context.findActivity() ?: return
try {
context.findActivity().setPictureInPictureParams(pipParams)
activity.setPictureInPictureParams(pipParams)
} catch (e: IllegalStateException) {
DebugLog.e(TAG, e.toString())
}
Expand Down Expand Up @@ -196,7 +203,7 @@ object PictureInPictureUtil {
}

private fun checkIsUserAllowPIP(context: ThemedReactContext): Boolean {
val activity = context.currentActivity ?: return false
val activity = context.getCurrentActivity() ?: return false
return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
@SuppressLint("InlinedApi")
val result = AppOpsManagerCompat.noteOpNoThrow(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,9 @@
import androidx.media3.exoplayer.source.DefaultMediaSourceFactory;
import androidx.media3.exoplayer.source.MediaSource;
import androidx.media3.exoplayer.source.MergingMediaSource;
import androidx.media3.extractor.Extractor;
import androidx.media3.extractor.text.DefaultSubtitleParserFactory;
import androidx.media3.extractor.text.SubtitleExtractor;
import androidx.media3.exoplayer.source.ProgressiveMediaSource;
import androidx.media3.exoplayer.source.TrackGroupArray;
import androidx.media3.exoplayer.source.ads.AdsMediaSource;
Expand Down Expand Up @@ -1144,12 +1147,48 @@ private MediaSource buildMediaSource(Uri uri, String overrideExtension, DrmSessi
? overridenMediaItemBuilder.build()
: mediaItemBuilder.build();

// Strip subtitle configurations before handing the MediaItem to the factory so that
// no factory variant (including overridden DefaultMediaSourceFactory) creates its own
// subtitle sources via legacy paths. We handle all subtitle merging below.
MediaItem mediaItemForSource = mediaItem.buildUpon()
.setSubtitleConfigurations(ImmutableList.of())
.build();
MediaSource mediaSource = mediaSourceFactory
.setDrmSessionManagerProvider(drmProvider)
.setLoadErrorHandlingPolicy(
config.buildLoadErrorHandlingPolicy(source.getMinLoadRetryCount())
)
.createMediaSource(mediaItem);
.createMediaSource(mediaItemForSource);

// ProgressiveMediaSource.Factory (and HLS/DASH variants) silently ignore
// MediaItem.SubtitleConfiguration entries. Manually merge sideloaded subtitle
// sources so they appear in MappedTrackInfo and can be selected via setSelectedTextTrack.
if (mediaItem.localConfiguration != null && !mediaItem.localConfiguration.subtitleConfigurations.isEmpty()) {
DataSource.Factory subtitleDataSourceFactory = buildDataSourceFactory(false);
DefaultSubtitleParserFactory subtitleParserFactory = new DefaultSubtitleParserFactory();
List<MediaSource> mergedSources = new ArrayList<>();
mergedSources.add(mediaSource);
for (MediaItem.SubtitleConfiguration subtitleConfig : mediaItem.localConfiguration.subtitleConfigurations) {
Format subtitleFormat = new Format.Builder()
.setSampleMimeType(subtitleConfig.mimeType)
.setLanguage(subtitleConfig.language)
.setLabel(subtitleConfig.label)
.setSelectionFlags(subtitleConfig.selectionFlags)
.setRoleFlags(subtitleConfig.roleFlags)
.build();
if (!subtitleParserFactory.supportsFormat(subtitleFormat)) {
DebugLog.w(TAG, "Skipping sideloaded subtitle track with unsupported format: " + subtitleConfig.mimeType);
continue;
}
mergedSources.add(new ProgressiveMediaSource.Factory(
subtitleDataSourceFactory,
() -> new Extractor[]{new SubtitleExtractor(subtitleParserFactory.create(subtitleFormat), subtitleFormat)}
).createMediaSource(MediaItem.fromUri(subtitleConfig.uri)));
}
if (mergedSources.size() > 1) {
mediaSource = new MergingMediaSource(mergedSources.toArray(new MediaSource[0]));
}
}

if (cropStartMs >= 0 && cropEndMs >= 0) {
return new ClippingMediaSource(mediaSource, cropStartMs * 1000, cropEndMs * 1000);
Expand Down
Loading