Skip to content

Feature/mediacontrol api#785

Open
tvanlaerhoven wants to merge 56 commits into
developfrom
feature/mediacontrol-api
Open

Feature/mediacontrol api#785
tvanlaerhoven wants to merge 56 commits into
developfrom
feature/mediacontrol-api

Conversation

@tvanlaerhoven

@tvanlaerhoven tvanlaerhoven commented Mar 25, 2026

Copy link
Copy Markdown
Member

Add MediaControl API.

  • Updated list of example sources to include posters, descriptions and consistent metadata.
  • Added a custom usePlaylist hook to allow switching sources through mediaSession & lock-screen controls.

Caution

Android needs an update of the media-session connector.


Open in Devin Review

@tvanlaerhoven
tvanlaerhoven force-pushed the feature/mediacontrol-api branch from 043c11c to 7246a54 Compare March 25, 2026 09:02
@tvanlaerhoven tvanlaerhoven added the enhancement New feature or request label Mar 25, 2026
@tvanlaerhoven
tvanlaerhoven force-pushed the feature/mediacontrol-api branch from 93ba7c2 to 0a8a959 Compare April 23, 2026 12:47
@tvanlaerhoven
tvanlaerhoven force-pushed the feature/mediacontrol-api branch 2 times, most recently from 864adcb to 8381de3 Compare May 8, 2026 08:30
devin-ai-integration[bot]

This comment was marked as resolved.

devin-ai-integration[bot]

This comment was marked as resolved.

devin-ai-integration[bot]

This comment was marked as resolved.

tvanlaerhoven and others added 3 commits July 24, 2026 14:48
Co-authored-by: devin-ai-integration[bot] <158243242+devin-ai-integration[bot]@users.noreply.github.com>
…t-native-theoplayer into feature/mediacontrol-api
devin-ai-integration[bot]

This comment was marked as resolved.

devin-ai-integration[bot]

This comment was marked as resolved.

devin-ai-integration[bot]

This comment was marked as resolved.

devin-ai-integration[bot]

This comment was marked as resolved.

devin-ai-integration[bot]

This comment was marked as resolved.

@devin-ai-integration devin-ai-integration Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Devin Review found 4 new potential issues.

Open in Devin Review

Comment thread src/internal/adapter/web/WebMediaSession.ts
Comment thread example/src/custom/SourceMenuButton.tsx Outdated
Comment on lines 295 to +300
// https://developer.android.com/media/legacy/media-buttons#restarting-inactive-mediasessions
this.mediaSession.setMediaButtonReceiver(null)
mediaSession.setMediaButtonReceiver(null)

// Install a queue navigator, but only if we want to handle skip buttons.
if (mediaSessionConfig.convertSkipToSeek) {
queueNavigator = MediaQueueNavigator(mediaSessionConfig)
}
addListener(mediaSessionListener)
// Route all media control actions through the MediaControlProxy, which will decide whether to
// invoke the action or not.
mediaControlProxy.attach(player, this, binder, config)

@devin-ai-integration devin-ai-integration Bot Jul 24, 2026

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔍 Live/ad play-pause no longer restricted at the media-session action level on Android

The removed applyAllowedMediaControls() used to set mediaSessionConnector.enabledPlaybackActions to 0 (or play/pause only) for live streams and ads, which hides/disables the corresponding lock-screen buttons. The new MediaControlProxy only guards the handlers (playPauseEnabled/trickPlayEnabled early-returns in onPlay/onPause/onFastForward/onRewind/onSeekTo at android/src/main/java/com/theoplayer/media/MediaControlProxy.kt:92-147) but no longer restricts the advertised enabledPlaybackActions on the connector. Depending on the media-session connector defaults, live streams (with allowLivePlayPause=false) and ads may now display play/pause/seek controls on the lock screen that do nothing when pressed. The PR description explicitly notes "Android needs an update of the media-session connector", so this may be a known, tracked gap — worth confirming the connector still gates the advertised actions.

Open in Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

Comment thread ios/backgroundAudio/THEOplayerRCTRemoteCommandsManager.swift

@devin-ai-integration devin-ai-integration Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Devin Review found 1 new potential issue.

Open in Devin Review

Comment on lines +60 to +83
useEffect(() => {
if (!player) return;

const handleNext = () => {
setCurrentIndex((index) => {
const newIndex = (index + 1) % filteredSources.length;
player.source = filteredSources[newIndex].source;
return newIndex;
});
};

const handlePrevious = () => {
setCurrentIndex((index) => {
const newIndex = (index - 1 + filteredSources.length) % filteredSources.length;
player.source = filteredSources[newIndex].source;
return newIndex;
});
};

// Install handlers for media control actions to enable playlist navigation using the media session API (e.g. lock
// screen controls, bluetooth controls, etc.)
player.mediaControl?.setHandler(MediaControlAction.SKIP_TO_NEXT, handleNext);
player.mediaControl?.setHandler(MediaControlAction.SKIP_TO_PREVIOUS, handlePrevious);
}, [player, filteredSources]);

@devin-ai-integration devin-ai-integration Bot Jul 24, 2026

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Lock-screen skip controls keep using a closed menu's stale state

The playlist hook registers its next/previous handlers (player.mediaControl?.setHandler at example/src/hooks/usePlaylist.ts:81-82) but never removes them, so when a second copy of the hook stops being used its handlers stay active and keep driving playback from outdated data.
Impact: After opening the source menu once, using the lock-screen/Bluetooth skip buttons navigates the playlist based on the closed menu's frozen position instead of the app's actual current item, sending playback to the wrong source.

Shared single handler slot with no effect cleanup

Both App (example/src/App.tsx:102) and SourceMenuView (example/src/custom/SourceMenuButton.tsx:22) call usePlaylist with the same player. The player exposes a single mediaControl adapter, whose native handler map holds one handler per action (src/internal/adapter/media/MediaControlNativeAdapter.ts:20-23), so the most recent setHandler call wins. When the menu opens, SourceMenuView's effect overwrites App's SKIP_TO_NEXT/SKIP_TO_PREVIOUS handlers with closures bound to the menu's own filteredSources/currentIndex state. The useEffect at example/src/hooks/usePlaylist.ts:60-83 returns no cleanup, so after the menu unmounts those handlers remain registered; subsequent lock-screen skips run setCurrentIndex on the unmounted instance and set player.source from the menu's state.

Open in Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

@devin-ai-integration devin-ai-integration Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Devin Review found 2 new potential issues.

Open in Devin Review

Comment thread ios/mediaControl/THEOplayerRCTMediaControlAPI.swift
Comment on lines +56 to +61
fun detach() {
connector?.apply {
queueNavigator = null
playbackCallback = null
}
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔍 Shared MediaSessionConnector across multiple players: detach can clobber the active player

With the background service enabled, all ReactTHEOplayerContext instances share the service's single mediaSessionConnector (MediaPlaybackService), but each context owns its own MediaControlProxy. applyMediaSessionConfig calls mediaControlProxy.detach() then attach(...) on that shared connector (ReactTHEOplayerContext.kt:280,299). detach() sets connector.queueNavigator = null and connector.playbackCallback = null (MediaControlProxy.kt:56-61) on the shared connector. If player B has taken ownership (set the connector's callback to proxyB) and player A subsequently re-applies its config, proxyA.detach() nulls out proxyB's callbacks, and proxyA.attach() then rebinds to A — a hand-off ordering hazard. The PR docs acknowledge single-owner responsibility, and the Android connector reportedly still needs updating (per PR caution note), so this is expected-fragile, but worth confirming the ownership guards in setMediaSessionEnabled fully cover this.

Open in Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants