Skip to content

Conversation

@peaBerberian
Copy link
Collaborator

@peaBerberian peaBerberian commented Jun 10, 2025

output2.mp4

Video: controlling playback from RxPaired.

This PR is a proposal where I implement a "control panel" on the bottom of RxPaired when at least one player is instantiated (it should work even with multiple players at the same time).

Its goal for now is to be compatible with the RxPlayer but also every other players: on the client-side, a new __RX_PAIRED_PLAYERS__ object is globally declared, with two methods, add and remove.
A common API has to be setup when add is called so that RxPaired can send commands (play / pause / reload etc.).

I did not yet implement some key advanced features for now: setting an audio/text/video track, changing the quality etc., as those require an event mechanism (advertising about available tracks and qualities for example) that I didn't specify for the moment to keep it simple.

To make it work with a player

To link the control panel to a player (the RxPlayer or any other player), special code has to be written on the player side, implementing the various available commands.

Players can even provide an incomplete implementation (with not all methods defined) in which case RxPaired will properly detect it and not display the unavailable ones in the inspector.

Here's the example of the implem I made for the RxPlayer:

diff --git src/main_thread/api/public_api.ts src/main_thread/api/public_api.ts
index 6cff1ef0d..4f5525796 100644
--- src/main_thread/api/public_api.ts
+++ src/main_thread/api/public_api.ts
@@ -111,6 +111,7 @@ import arrayIncludes from "../../utils/array_includes";
 import assert, { assertUnreachable } from "../../utils/assert";
 import type { IEventPayload, IListener } from "../../utils/event_emitter";
 import EventEmitter from "../../utils/event_emitter";
+import globalScope from "../../utils/global_scope";
 import idGenerator from "../../utils/id_generator";
 import isNullOrUndefined from "../../utils/is_null_or_undefined";
 import type Logger from "../../utils/logger";
@@ -475,6 +476,10 @@ class Player extends EventEmitter<IPublicAPIEvent> {
     destroyCanceller.signal.register(() => {
       videoElement.removeEventListener("volumechange", onVolumeChange);
     });
+
+    if (isDebugModeEnabled()) {
+      registerPlayerForRxPaired(this);
+    }
   }
 
   /**
@@ -685,6 +690,10 @@ class Player extends EventEmitter<IPublicAPIEvent> {
       this._priv_worker.terminate();
       this._priv_worker = null;
     }
+
+    if (isDebugModeEnabled()) {
+      unregisterPlayerForRxPaired(this);
+    }
   }
 
   /**
@@ -3572,4 +3581,83 @@ export interface IPublicApiContentInfos {
   };
 }
 
+function registerPlayerForRxPaired(player: Player): void {
+  (
+    globalScope as typeof globalScope & {
+      __RX_PAIRED_PLAYERS__?: IRxPairedPlayersObject | undefined;
+    }
+  ).__RX_PAIRED_PLAYERS__?.add({
+    version: 1,
+    name: "RxPlayer",
+    key: player,
+    obj: {
+      seekAbsolute: (args: string[]) => {
+        const pos = +args[0];
+        if (!isNaN(pos)) {
+          player.seekTo(pos);
+        }
+      },
+      seekRelative: (args: string[]) => {
+        const pos = +args[0];
+        if (!isNaN(pos)) {
+          player.seekTo({ relative: pos });
+        }
+      },
+      setPlaybackRate: (args: string[]) => {
+        const pr = +args[0];
+        if (!isNaN(pr)) {
+          player.setPlaybackRate(pr);
+        }
+      },
+      setWantedBufferAhead: (args: string[]) => {
+        const wba = +args[0];
+        if (!isNaN(wba)) {
+          player.setWantedBufferAhead(wba);
+        }
+      },
+      mute: () => player.mute(),
+      unmute: () => player.unMute(),
+      reload: () => player.reload(),
+      pause: () => player.pause(),
+      resume: () => player.play(),
+      stop: () => player.stop(),
+    },
+  });
+}
+
+function unregisterPlayerForRxPaired(player: Player): void {
+  (
+    globalScope as typeof globalScope & {
+      __RX_PAIRED_PLAYERS__?: IRxPairedPlayersObject | undefined;
+    }
+  ).__RX_PAIRED_PLAYERS__?.remove(player);
+}
+
+interface IRxPairedPlayersObject {
+  add: (arg: { version: number; name: string; key: unknown; obj: unknown }) => void;
+  remove: (key: unknown) => void;
+}
+
 export default Player;

@peaBerberian peaBerberian force-pushed the control-panel branch 12 times, most recently from 9fcbaf0 to 14a66c5 Compare June 11, 2025 11:48
@peaBerberian peaBerberian changed the title [WIP] Control panel [Proposal] Control panel Jun 11, 2025
@peaBerberian peaBerberian force-pushed the control-panel branch 6 times, most recently from e742123 to 830f84d Compare June 11, 2025 20:39
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants