add autostart button to videoplayer

This commit is contained in:
CJ 2025-12-03 00:12:49 -06:00
parent 730e877e73
commit 302419739b
3 changed files with 183 additions and 5 deletions

View file

@ -16,6 +16,7 @@ import "./live";
import "./PlaylistButtons"; import "./PlaylistButtons";
import "./source-selector"; import "./source-selector";
import "./persist-volume"; import "./persist-volume";
import "./autostart-button";
import MarkersPlugin, { type IMarker } from "./markers"; import MarkersPlugin, { type IMarker } from "./markers";
void MarkersPlugin; void MarkersPlugin;
import "./vtt-thumbnails"; import "./vtt-thumbnails";
@ -389,6 +390,9 @@ export const ScenePlayer: React.FC<IScenePlayerProps> = PatchComponent(
skipButtons: {}, skipButtons: {},
trackActivity: {}, trackActivity: {},
vrMenu: {}, vrMenu: {},
autostartButton: {
enabled: interfaceConfig?.autostartVideo ?? false,
},
abLoopPlugin: { abLoopPlugin: {
start: 0, start: 0,
end: false, end: false,
@ -675,11 +679,6 @@ export const ScenePlayer: React.FC<IScenePlayerProps> = PatchComponent(
} }
} }
auto.current =
autoplay ||
(interfaceConfig?.autostartVideo ?? false) ||
_initialTimestamp > 0;
const alwaysStartFromBeginning = const alwaysStartFromBeginning =
uiConfig?.alwaysStartFromBeginning ?? false; uiConfig?.alwaysStartFromBeginning ?? false;
const resumeTime = scene.resume_time ?? 0; const resumeTime = scene.resume_time ?? 0;
@ -698,6 +697,22 @@ export const ScenePlayer: React.FC<IScenePlayerProps> = PatchComponent(
player.load(); player.load();
player.focus(); player.focus();
// Check the autostart button plugin for user preference
const autostartButton = player.autostartButton();
autostartButton.getEnabled().then((buttonEnabled) => {
auto.current =
autoplay ||
buttonEnabled ||
(interfaceConfig?.autostartVideo ?? false) ||
_initialTimestamp > 0;
// Trigger autoplay if conditions are met and player is ready
if (auto.current && ready && player.paused()) {
player.play();
auto.current = false;
}
});
player.ready(() => { player.ready(() => {
player.vttThumbnails().src(scene.paths.vtt ?? null); player.vttThumbnails().src(scene.paths.vtt ?? null);

View file

@ -0,0 +1,131 @@
/* eslint-disable @typescript-eslint/naming-convention */
import videojs, { VideoJsPlayer } from "video.js";
import localForage from "localforage";
const AUTOSTART_KEY = "video-autostart-enabled";
interface IAutostartButtonOptions {
enabled?: boolean;
}
interface AutostartButtonOptions extends videojs.ComponentOptions {
autostartEnabled: boolean;
}
class AutostartButton extends videojs.getComponent("Button") {
private autostartEnabled: boolean;
constructor(player: VideoJsPlayer, options: AutostartButtonOptions) {
super(player, options);
this.autostartEnabled = options.autostartEnabled;
this.updateIcon();
}
buildCSSClass() {
return `vjs-autostart-button ${super.buildCSSClass()}`;
}
private updateIcon() {
this.removeClass("vjs-icon-play-circle");
this.removeClass("vjs-icon-cancel");
if (this.autostartEnabled) {
this.addClass("vjs-icon-play-circle");
this.controlText(this.localize("Auto-start enabled (click to disable)"));
} else {
this.addClass("vjs-icon-cancel");
this.controlText(this.localize("Auto-start disabled (click to enable)"));
}
}
handleClick() {
this.autostartEnabled = !this.autostartEnabled;
this.updateIcon();
this.trigger("autostartchanged", { enabled: this.autostartEnabled });
}
public setEnabled(enabled: boolean) {
this.autostartEnabled = enabled;
this.updateIcon();
}
}
class AutostartButtonPlugin extends videojs.getPlugin("plugin") {
private button: AutostartButton;
private autostartEnabled: boolean;
private loaded: boolean = false;
constructor(player: VideoJsPlayer, options?: IAutostartButtonOptions) {
super(player, options);
this.autostartEnabled = options?.enabled ?? false;
// Load the saved preference immediately
this.loadPreference();
this.button = new AutostartButton(player, {
autostartEnabled: this.autostartEnabled,
});
player.ready(() => {
this.ready();
});
}
private async loadPreference() {
const value = await localForage.getItem<boolean>(AUTOSTART_KEY);
if (value !== null) {
this.autostartEnabled = value;
if (this.button) {
this.button.setEnabled(value);
}
}
this.loaded = true;
}
private ready() {
// Add button to control bar, before the fullscreen button
const controlBar = this.player.controlBar;
const fullscreenToggle = controlBar.getChild("fullscreenToggle");
if (fullscreenToggle) {
controlBar.addChild(this.button);
controlBar.el().insertBefore(this.button.el(), fullscreenToggle.el());
} else {
controlBar.addChild(this.button);
}
// Listen for changes
this.button.on("autostartchanged", (_, data: { enabled: boolean }) => {
this.autostartEnabled = data.enabled;
localForage.setItem(AUTOSTART_KEY, data.enabled);
});
}
public isEnabled(): boolean {
return this.autostartEnabled;
}
public async getEnabled(): Promise<boolean> {
// Wait for the preference to be loaded if it hasn't been yet
if (!this.loaded) {
await this.loadPreference();
}
return this.autostartEnabled;
}
}
// Register the plugin with video.js.
videojs.registerComponent("AutostartButton", AutostartButton);
videojs.registerPlugin("autostartButton", AutostartButtonPlugin);
declare module "video.js" {
interface VideoJsPlayer {
autostartButton: () => AutostartButtonPlugin;
}
interface VideoJsPlayerPluginOptions {
autostartButton?: IAutostartButtonOptions;
}
}
export default AutostartButtonPlugin;

View file

@ -100,6 +100,38 @@ $sceneTabWidth: 450px;
width: 1.6em; width: 1.6em;
} }
.vjs-autostart-button {
cursor: pointer;
// use repeat/loop icon
&.vjs-icon-play-circle::before {
align-items: center;
background-color: rgba(80, 80, 80, 0.9);
border-radius: 50%;
color: #fff;
content: "\f101";
font-size: 1em;
line-height: 1;
padding: 0.3em;
}
&.vjs-icon-cancel::before {
align-items: center;
background-color: rgba(255, 255, 255, 0.9);
border-radius: 50%;
color: rgba(80, 80, 80, 0.9);
content: "\f103";
font-size: 1em;
line-height: 1;
padding: 0.3em;
}
&:hover {
text-shadow: 0 0 1em rgba(255, 255, 255, 0.75);
}
.vjs-touch-overlay .vjs-play-control { .vjs-touch-overlay .vjs-play-control {
z-index: 1; z-index: 1;
} }