mirror of
https://github.com/zoriya/react-native-video.git
synced 2025-12-06 07:16:12 +00:00
Handle media sessions
This commit is contained in:
@@ -11,7 +11,8 @@ import {
|
||||
import type { VideoPlayerBase } from "./types/VideoPlayerBase";
|
||||
import type { VideoPlayerStatus } from "./types/VideoPlayerStatus";
|
||||
import { VideoPlayerEvents } from "./VideoPlayerEvents";
|
||||
import { WebEventEmiter } from "./WebEventEmiter";
|
||||
import { MediaSessionHandler } from "./web/MediaSession";
|
||||
import { WebEventEmiter } from "./web/WebEventEmiter";
|
||||
import videojs from "video.js";
|
||||
|
||||
type VideoJsPlayer = ReturnType<typeof videojs>;
|
||||
@@ -33,6 +34,7 @@ type VideoJsTextTracks = {
|
||||
class VideoPlayer extends VideoPlayerEvents implements VideoPlayerBase {
|
||||
protected video: HTMLVideoElement;
|
||||
public player: VideoJsPlayer;
|
||||
private mediaSession: MediaSessionHandler;
|
||||
|
||||
constructor(source: VideoSource | VideoConfig | VideoPlayerSource) {
|
||||
const video = document.createElement("video");
|
||||
@@ -42,6 +44,7 @@ class VideoPlayer extends VideoPlayerEvents implements VideoPlayerBase {
|
||||
|
||||
this.video = video;
|
||||
this.player = player;
|
||||
this.mediaSession = new MediaSessionHandler(this.player);
|
||||
|
||||
this.replaceSourceAsync(source);
|
||||
}
|
||||
@@ -153,40 +156,48 @@ class VideoPlayer extends VideoPlayerEvents implements VideoPlayerBase {
|
||||
return "auto";
|
||||
}
|
||||
|
||||
set mixAudioMode(_: MixAudioMode) { }
|
||||
set mixAudioMode(_: MixAudioMode) {}
|
||||
|
||||
// Ignore Silent Switch Mode
|
||||
get ignoreSilentSwitchMode(): IgnoreSilentSwitchMode {
|
||||
return "auto";
|
||||
}
|
||||
|
||||
set ignoreSilentSwitchMode(_: IgnoreSilentSwitchMode) { }
|
||||
set ignoreSilentSwitchMode(_: IgnoreSilentSwitchMode) {}
|
||||
|
||||
// Play In Background
|
||||
get playInBackground(): boolean {
|
||||
return true;
|
||||
}
|
||||
|
||||
set playInBackground(_: boolean) { }
|
||||
set playInBackground(_: boolean) {}
|
||||
|
||||
// Play When Inactive
|
||||
get playWhenInactive(): boolean {
|
||||
return true;
|
||||
}
|
||||
|
||||
set playWhenInactive(_: boolean) { }
|
||||
set playWhenInactive(_: boolean) {}
|
||||
|
||||
// Is Playing
|
||||
get isPlaying(): boolean {
|
||||
return !this.player.paused();
|
||||
}
|
||||
|
||||
get showNotificationControls(): boolean {
|
||||
return this.mediaSession.enabled;
|
||||
}
|
||||
|
||||
set showNotificationControls(value: boolean) {
|
||||
if (value) this.mediaSession.enable();
|
||||
else this.mediaSession.disable();
|
||||
}
|
||||
|
||||
async initialize(): Promise<void> {
|
||||
// noop on web
|
||||
}
|
||||
|
||||
async preload(): Promise<void> {
|
||||
this.player.load()
|
||||
this.player.load();
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -141,7 +141,7 @@ interface NativeDrmParams extends DrmParams {
|
||||
type?: string;
|
||||
}
|
||||
|
||||
interface CustomVideoMetadata {
|
||||
export interface CustomVideoMetadata {
|
||||
title?: string;
|
||||
subtitle?: string;
|
||||
description?: string;
|
||||
|
||||
140
packages/react-native-video/src/core/web/MediaSession.ts
Normal file
140
packages/react-native-video/src/core/web/MediaSession.ts
Normal file
@@ -0,0 +1,140 @@
|
||||
import type videojs from "video.js";
|
||||
import type { CustomVideoMetadata } from "../types/VideoConfig";
|
||||
|
||||
type VideoJsPlayer = ReturnType<typeof videojs>;
|
||||
|
||||
const mediaSession = window.navigator.mediaSession;
|
||||
|
||||
export class MediaSessionHandler {
|
||||
enabled: boolean = false;
|
||||
|
||||
constructor(private player: VideoJsPlayer) {}
|
||||
|
||||
enable() {
|
||||
this.enabled = true;
|
||||
|
||||
const defaultSkipTime = 15;
|
||||
|
||||
const actionHandlers = [
|
||||
[
|
||||
"play",
|
||||
() => {
|
||||
this.player.play();
|
||||
},
|
||||
],
|
||||
[
|
||||
"pause",
|
||||
() => {
|
||||
this.player.pause();
|
||||
},
|
||||
],
|
||||
[
|
||||
"stop",
|
||||
() => {
|
||||
this.player.pause();
|
||||
this.player.currentTime(0);
|
||||
},
|
||||
],
|
||||
// videojs-contrib-ads
|
||||
[
|
||||
"seekbackward",
|
||||
(details: MediaSessionActionDetails) => {
|
||||
if (this.player.usingPlugin("ads") && this.player.ads.inAdBreak()) {
|
||||
return;
|
||||
}
|
||||
this.player.currentTime(
|
||||
Math.max(
|
||||
0,
|
||||
(this.player.currentTime() ?? 0) -
|
||||
(details.seekOffset || defaultSkipTime),
|
||||
),
|
||||
);
|
||||
},
|
||||
],
|
||||
[
|
||||
"seekforward",
|
||||
(details: MediaSessionActionDetails) => {
|
||||
if (this.player.usingPlugin("ads") && this.player.ads.inAdBreak()) {
|
||||
return;
|
||||
}
|
||||
this.player.currentTime(
|
||||
Math.min(
|
||||
this.player.duration() ?? 0,
|
||||
(this.player.currentTime() ?? 0) +
|
||||
(details.seekOffset || defaultSkipTime),
|
||||
),
|
||||
);
|
||||
},
|
||||
],
|
||||
[
|
||||
"seekto",
|
||||
(details: MediaSessionActionDetails) => {
|
||||
if (this.player.usingPlugin("ads") && this.player.ads.inAdBreak()) {
|
||||
return;
|
||||
}
|
||||
this.player.currentTime(details.seekTime);
|
||||
},
|
||||
],
|
||||
] as const;
|
||||
|
||||
for (const [action, handler] of actionHandlers) {
|
||||
try {
|
||||
mediaSession.setActionHandler(action, handler);
|
||||
} catch {
|
||||
this.player.log.debug(
|
||||
`Couldn't register media session action "${action}".`,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const onPlaying = () => {
|
||||
mediaSession.playbackState = "playing";
|
||||
};
|
||||
const onPaused = () => {
|
||||
mediaSession.playbackState = "paused";
|
||||
};
|
||||
const onTimeUpdate = () => {
|
||||
const dur = this.player.duration();
|
||||
|
||||
if (Number.isFinite(dur)) {
|
||||
mediaSession.setPositionState({
|
||||
duration: dur,
|
||||
playbackRate: this.player.playbackRate(),
|
||||
position: this.player.currentTime(),
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
this.player.on("playing", onPlaying);
|
||||
this.player.on("paused", onPaused);
|
||||
if ("setPositionState" in mediaSession) {
|
||||
this.player.on("timeupdate", onTimeUpdate);
|
||||
}
|
||||
|
||||
this.disable = () => {
|
||||
this.enabled = false;
|
||||
this.player.off("playing", onPlaying);
|
||||
this.player.off("paused", onPaused);
|
||||
if ("setPositionState" in mediaSession) {
|
||||
this.player.off("timeupdate", onTimeUpdate);
|
||||
}
|
||||
mediaSession.metadata = null;
|
||||
for (const [action, _] of actionHandlers) {
|
||||
try {
|
||||
mediaSession.setActionHandler(action, null);
|
||||
} catch {}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
disable() {}
|
||||
|
||||
updateMediaSession(metadata: CustomVideoMetadata) {
|
||||
mediaSession.metadata = new window.MediaMetadata({
|
||||
title: metadata.title,
|
||||
album: metadata.subtitle,
|
||||
artist: metadata.artist,
|
||||
artwork: metadata.imageUri ? [{ src: metadata.imageUri }] : [],
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -8,10 +8,10 @@ import type {
|
||||
onVolumeChangeData,
|
||||
AllPlayerEvents as PlayerEvents,
|
||||
TimedMetadata,
|
||||
} from "./types/Events";
|
||||
import type { TextTrack } from "./types/TextTrack";
|
||||
import type { VideoRuntimeError } from "./types/VideoError";
|
||||
import type { VideoPlayerStatus } from "./types/VideoPlayerStatus";
|
||||
} from "../types/Events";
|
||||
import type { TextTrack } from "../types/TextTrack";
|
||||
import type { VideoRuntimeError } from "../types/VideoError";
|
||||
import type { VideoPlayerStatus } from "../types/VideoPlayerStatus";
|
||||
|
||||
type VideoJsPlayer = ReturnType<typeof videojs>;
|
||||
|
||||
Reference in New Issue
Block a user