mirror of
https://github.com/hensm/fx_cast.git
synced 2026-06-10 17:49:58 +00:00
370 lines
10 KiB
TypeScript
370 lines
10 KiB
TypeScript
"use strict";
|
|
|
|
import { Options } from "../defaultOptions";
|
|
import cast, { init } from "../shim/export";
|
|
|
|
|
|
// Variables passed from background
|
|
const { srcUrl
|
|
, targetElementId }
|
|
: { srcUrl: string
|
|
, targetElementId: number } = (window as any);
|
|
|
|
|
|
let options: Options;
|
|
|
|
let session: cast.Session;
|
|
let currentMedia: cast.media.Media;
|
|
|
|
let ignoreMediaEvents = false;
|
|
|
|
|
|
const isLocalFile = srcUrl.startsWith("file:");
|
|
|
|
const mediaElement = browser.menus.getTargetElement(
|
|
targetElementId) as HTMLMediaElement;
|
|
|
|
window.addEventListener("beforeunload", () => {
|
|
browser.runtime.sendMessage({
|
|
subject: "bridge:/mediaServer/stop"
|
|
});
|
|
|
|
if (options.mediaStopOnUnload) {
|
|
session.stop(null, null);
|
|
/*currentMedia.stop(null
|
|
, onMediaStopSuccess
|
|
, onMediaStopError);*/
|
|
}
|
|
});
|
|
|
|
function getLocalAddress () {
|
|
const pc = new RTCPeerConnection();
|
|
pc.createDataChannel(null);
|
|
pc.createOffer().then(pc.setLocalDescription.bind(pc));
|
|
|
|
return new Promise((resolve, reject) => {
|
|
pc.addEventListener("icecandidate", ev => {
|
|
if (ev.candidate) {
|
|
resolve(ev.candidate.candidate.split(" ")[4]);
|
|
}
|
|
});
|
|
pc.addEventListener("error", ev => {
|
|
reject();
|
|
});
|
|
});
|
|
}
|
|
|
|
|
|
async function onRequestSessionSuccess (session_: cast.Session) {
|
|
cast.logMessage("onRequestSessionSuccess");
|
|
|
|
session = session_;
|
|
|
|
let mediaUrl = new URL(srcUrl);
|
|
const port = options.localMediaServerPort;
|
|
|
|
if (isLocalFile) {
|
|
await new Promise((resolve, reject) => {
|
|
browser.runtime.sendMessage({
|
|
subject: "bridge:/mediaServer/start"
|
|
, data: {
|
|
filePath: decodeURI(mediaUrl.pathname)
|
|
, port
|
|
}
|
|
});
|
|
|
|
browser.runtime.onMessage.addListener(function onMessage (message) {
|
|
if (message.subject === "mediaCast:/mediaServer/started") {
|
|
browser.runtime.onMessage.removeListener(onMessage);
|
|
resolve();
|
|
}
|
|
});
|
|
});
|
|
|
|
// Address of local HTTP server
|
|
mediaUrl = new URL(`http://${await getLocalAddress()}:${port}/`);
|
|
}
|
|
|
|
const mediaInfo = new cast.media.MediaInfo(mediaUrl.href, null);
|
|
|
|
// Media metadata (title/poster)
|
|
mediaInfo.metadata = new cast.media.GenericMediaMetadata();
|
|
mediaInfo.metadata.metadataType = cast.media.MetadataType.GENERIC;
|
|
mediaInfo.metadata.title = mediaUrl.pathname;
|
|
|
|
if (mediaElement instanceof HTMLVideoElement && mediaElement.poster) {
|
|
mediaInfo.metadata.images = [
|
|
new cast.Image(mediaElement.poster)
|
|
];
|
|
}
|
|
|
|
|
|
const activeTrackIds = [];
|
|
|
|
if (mediaElement.textTracks.length) {
|
|
const trackElements = mediaElement.querySelectorAll("track");
|
|
|
|
let index = 0;
|
|
for (const textTrack of Array.from(mediaElement.textTracks)) {
|
|
const trackElement = trackElements[index];
|
|
|
|
// Create Track object
|
|
const track = new cast.media.Track(
|
|
index // trackId
|
|
, cast.media.TrackType.TEXT); // trackType
|
|
|
|
// Copy TextTrack properties to Track
|
|
track.name = textTrack.label;
|
|
track.language = textTrack.language;
|
|
track.trackContentId = trackElement.src;
|
|
track.trackContentType = "text/vtt";
|
|
|
|
const { TextTrackType } = cast.media;
|
|
|
|
switch (textTrack.kind) {
|
|
case "subtitles":
|
|
track.subtype = TextTrackType.SUBTITLES;
|
|
break;
|
|
case "captions":
|
|
track.subtype = TextTrackType.CAPTIONS;
|
|
break;
|
|
case "descriptions":
|
|
track.subtype = TextTrackType.DESCRIPTIONS;
|
|
break;
|
|
case "chapters":
|
|
track.subtype = TextTrackType.CHAPTERS;
|
|
break;
|
|
case "metadata":
|
|
track.subtype = TextTrackType.METADATA;
|
|
break;
|
|
|
|
// Default to subtitles
|
|
default:
|
|
track.subtype = TextTrackType.SUBTITLES;
|
|
}
|
|
|
|
// Add track to mediaInfo
|
|
mediaInfo.tracks.push(track);
|
|
|
|
// If enabled, set as active track for load request
|
|
if (textTrack.mode === "showing" || trackElement.default) {
|
|
activeTrackIds.push(index);
|
|
}
|
|
|
|
index++;
|
|
}
|
|
}
|
|
|
|
const loadRequest = new cast.media.LoadRequest(mediaInfo);
|
|
loadRequest.autoplay = false;
|
|
loadRequest.activeTrackIds = activeTrackIds;
|
|
|
|
session.loadMedia(loadRequest
|
|
, onLoadMediaSuccess
|
|
, onLoadMediaError);
|
|
}
|
|
function onRequestSessionError () {
|
|
cast.logMessage("onRequestSessionError");
|
|
}
|
|
|
|
function sessionListener (session: cast.Session) {
|
|
cast.logMessage("sessionListener");
|
|
}
|
|
function receiverListener (availability: string) {
|
|
cast.logMessage("receiverListener");
|
|
|
|
if (availability === cast.ReceiverAvailability.AVAILABLE) {
|
|
cast.requestSession(
|
|
onRequestSessionSuccess
|
|
, onRequestSessionError);
|
|
}
|
|
}
|
|
|
|
function onInitializeSuccess () {
|
|
cast.logMessage("onInitializeSuccess");
|
|
}
|
|
function onInitializeError () {
|
|
cast.logMessage("onInitializeError");
|
|
}
|
|
|
|
function onLoadMediaSuccess (media: cast.media.Media) {
|
|
cast.logMessage("onLoadMediaSuccess");
|
|
|
|
currentMedia = media;
|
|
|
|
if (options.mediaSyncElement) {
|
|
mediaElement.addEventListener("play", () => {
|
|
if (ignoreMediaEvents) {
|
|
ignoreMediaEvents = false;
|
|
return;
|
|
}
|
|
|
|
currentMedia.play(null
|
|
, onMediaPlaySuccess
|
|
, onMediaPlayError);
|
|
});
|
|
|
|
mediaElement.addEventListener("pause", () => {
|
|
if (ignoreMediaEvents) {
|
|
ignoreMediaEvents = false;
|
|
return;
|
|
}
|
|
|
|
currentMedia.pause(null
|
|
, onMediaPauseSuccess
|
|
, onMediaPauseError);
|
|
});
|
|
|
|
mediaElement.addEventListener("suspend", () => {
|
|
/*currentMedia.stop(null
|
|
, onMediaStopSuccess
|
|
, onMediaStopError);*/
|
|
});
|
|
|
|
mediaElement.addEventListener("seeking", () => {
|
|
if (ignoreMediaEvents) {
|
|
ignoreMediaEvents = false;
|
|
return;
|
|
}
|
|
|
|
const seekRequest = new cast.media.SeekRequest();
|
|
seekRequest.currentTime = mediaElement.currentTime;
|
|
|
|
currentMedia.seek(seekRequest
|
|
, onMediaSeekSuccess
|
|
, onMediaSeekError);
|
|
});
|
|
|
|
mediaElement.addEventListener("ratechange", () => {
|
|
(currentMedia as any)._sendMediaMessage({
|
|
type: "SET_PLAYBACK_RATE"
|
|
, playbackRate: mediaElement.playbackRate
|
|
});
|
|
});
|
|
|
|
mediaElement.addEventListener("volumechange", () => {
|
|
const newVolume = new cast.Volume(
|
|
currentMedia.volume.level
|
|
, currentMedia.volume.muted);
|
|
|
|
const volumeRequest =
|
|
new cast.media.VolumeRequest(newVolume);
|
|
|
|
cast.logMessage("Volume change");
|
|
currentMedia.setVolume(volumeRequest);
|
|
});
|
|
|
|
|
|
currentMedia.addUpdateListener(isAlive => {
|
|
if (!isAlive) {
|
|
return;
|
|
}
|
|
|
|
// PlayerState
|
|
const localPlayerState = mediaElement.paused
|
|
? cast.media.PlayerState.PAUSED
|
|
: cast.media.PlayerState.PLAYING;
|
|
|
|
if (localPlayerState !== currentMedia.playerState) {
|
|
ignoreMediaEvents = true;
|
|
switch (currentMedia.playerState) {
|
|
case cast.media.PlayerState.PLAYING:
|
|
mediaElement.play();
|
|
break;
|
|
|
|
case cast.media.PlayerState.PAUSED:
|
|
mediaElement.pause();
|
|
break;
|
|
}
|
|
}
|
|
|
|
// RepeatMode
|
|
const localRepeatMode = mediaElement.loop
|
|
? cast.media.RepeatMode.SINGLE
|
|
: cast.media.RepeatMode.OFF;
|
|
|
|
if (localRepeatMode !== currentMedia.repeatMode) {
|
|
ignoreMediaEvents = true;
|
|
switch (currentMedia.repeatMode) {
|
|
case cast.media.RepeatMode.SINGLE:
|
|
mediaElement.loop = true;
|
|
break;
|
|
|
|
case cast.media.RepeatMode.OFF:
|
|
mediaElement.loop = false;
|
|
break;
|
|
}
|
|
}
|
|
|
|
|
|
// currentTime
|
|
if (currentMedia.currentTime !== mediaElement.currentTime) {
|
|
ignoreMediaEvents = true;
|
|
mediaElement.currentTime = currentMedia.currentTime;
|
|
}
|
|
});
|
|
}
|
|
}
|
|
function onLoadMediaError () {
|
|
cast.logMessage("onLoadMediaError");
|
|
}
|
|
|
|
/* play */
|
|
function onMediaPlaySuccess () {
|
|
cast.logMessage("onMediaPlaySuccess");
|
|
}
|
|
function onMediaPlayError (err: cast.Error) {
|
|
cast.logMessage("onMediaPlayError");
|
|
}
|
|
|
|
/* pause */
|
|
function onMediaPauseSuccess () {
|
|
cast.logMessage("onMediaPauseSuccess");
|
|
}
|
|
function onMediaPauseError (err: cast.Error) {
|
|
cast.logMessage("onMediaPauseError");
|
|
}
|
|
|
|
/* stop */
|
|
function onMediaStopSuccess () {
|
|
cast.logMessage("onMediaStopSuccess");
|
|
}
|
|
function onMediaStopError (err: cast.Error) {
|
|
cast.logMessage("onMediaStopError");
|
|
}
|
|
|
|
/* seek */
|
|
function onMediaSeekSuccess () {
|
|
cast.logMessage("onMediaSeekSuccess");
|
|
}
|
|
function onMediaSeekError (err: cast.Error) {
|
|
cast.logMessage("onMediaSeekError");
|
|
}
|
|
|
|
|
|
init().then(async bridgeInfo => {
|
|
if (!bridgeInfo.isVersionCompatible) {
|
|
console.error("__onGCastApiAvailable error");
|
|
return;
|
|
}
|
|
|
|
options = (await browser.storage.sync.get("options")).options;
|
|
|
|
if (isLocalFile && !options.localMediaEnabled) {
|
|
cast.logMessage("Local media casting not enabled");
|
|
return;
|
|
}
|
|
|
|
|
|
const sessionRequest = new cast.SessionRequest(
|
|
cast.media.DEFAULT_MEDIA_RECEIVER_APP_ID);
|
|
|
|
const apiConfig = new cast.ApiConfig(sessionRequest
|
|
, sessionListener
|
|
, receiverListener);
|
|
|
|
cast.initialize(apiConfig
|
|
, onInitializeSuccess
|
|
, onInitializeError);
|
|
});
|