// h横屏 v竖屏
var device;
var voice_url = getQueryString("voice_url");
const streamUrl = getQueryString("url");
console.log("streamUrl ", streamUrl);
if (/(iPhone|iPad|iPod|iOS|Android)/i.test(navigator.userAgent)) {
//手机开启vconsole,便于查看控制台调试信息,正式部署时无需使用,包括上面的vconsole.min.js也不用引入
//var vConsole = new VConsole();
}
device = getQueryString("d");
function initScreen() {
if (device === "v") {
document.querySelector("#h").style.display = "none";
} else {
document.querySelector("#v").style.display = "none";
}
}
initScreen();
var player;
var voicePlayer;
var isPlayerInitialized = false;
var messageQueue = [];
// 创建一个观察者对象
const playerHandler = {
set(target, property, value) {
// 设置属性值
target[property] = value;
// 检查是否是初始化完成的标志
if (property === "initialized" && value === true) {
isPlayerInitialized = true;
// 处理队列中的消息
while (messageQueue.length > 0) {
var message = messageQueue.shift();
handleMessage(message);
}
}
// 返回 true 表示成功设置属性值
return true;
},
};
// 创建代理对象
const observedPlayer = new Proxy({}, playerHandler);
// #region 控件区域显示
var videoWrap = document.querySelector(".video-wrap");
videoWrap.style.height = window.innerHeight + "px !important";
var mute = document.querySelector(".mute");
var trumpet = document.querySelector(".trumpet");
var overlay = document.querySelector(".overlay");
var controlWrap = document.querySelector(".control-wrap");
function toggleSound(e) {
e.stopPropagation();
if (controlWrap.getAttribute("status") === "visible") {
controlWrap.setAttribute("status", "hidden");
controlWrap.style.opacity = 0;
controlWrap.style.visibility = "hidden";
controlWrap.style.transform = "translateX(15vw)";
} else {
controlWrap.setAttribute("status", "visible");
controlWrap.style.opacity = 1;
controlWrap.style.visibility = "visible";
controlWrap.style.transform = "translateX(0vw)";
}
}
/**
* 判断视频是否有声音
* @param {HTMLVideoElement} video
* @returns {Boolean}
*/
async function checkAudioOutputDevices() {
let devices = await navigator.mediaDevices.enumerateDevices();
let audioOutputDevices = devices.filter(
(device) => device.kind === "audiooutput"
);
return audioOutputDevices.length > 0;
}
function initControlStatus() {
closeVolume();
controlWrap.setAttribute("status", "visible");
controlWrap.style.opacity = 1;
// 初始化播放/暂停按钮状态
const playPauseBtn = document.getElementById("playPauseBtn");
if (playPauseBtn && streamUrl) {
playPauseBtn.src = "static/picture/pause.svg"; // 有流地址时显示暂停按钮
isPlaying = true;
} else if (playPauseBtn) {
playPauseBtn.src = "static/picture/play.svg"; // 无流地址时显示播放按钮
isPlaying = false;
}
}
// #endregion
function initVideo() {
NodePlayer.load(() => {
/**
* 是否打印debug信息
*/
NodePlayer.debug(false);
player = new NodePlayer();
/**
* 自动测试浏览器是否支持MSE播放,如不支持,仍然使用软解码。
* 紧随 new 后调用
* 不调用则只使用软解
*/
player.useMSE();
/**
* 使用Chrome86及之后提供的WebCodecs API来进行硬解码,当前为实验特性,需要手动开启
* 在浏览器地址栏输入 chrome://flags/#enable-experimental-web-platform-features 设为启动
* 或者在命令行中加参数 --enable-blink-features=WebCodecs 来启动
* Chrome最新发布94版已默认开启,支持Desktop,Anddroid,Webview!
* 需要https加载web,播放https/wss流地址
*/
// player.useWCS();
/**
* 传入 canvas视图的id,当使用mse时,自动转换为video标签
*/
player.setView(device === "v" ? "v" : "h");
/**
* 是否开启屏幕常亮
* 在手机浏览器上,canvas标签渲染视频并不会像video标签那样保持屏幕常亮
* 如果需要该功能, 可以调用此方法, 会有少量cpu消耗, pc浏览器不会执行
*/
// player.setKeepScreenOn();
/**
* 设置为等比缩放模式
* 横:h
* 竖:v
* 设置视频缩放模式
* 当视频分辨率比例与canvas显示区域比例不同时,缩放效果不同:
* 0 视频画面完全填充canvas区域,画面会被拉伸
* 1 视频画面做等比缩放后,对齐canvas区域,画面不被拉伸,但有黑边
* 2 视频画面做等比缩放后,完全填充canvas区域,画面不被拉伸,没有黑边,但画面显示不全
* 注意:只在软解时有效
* @param mode 缩放模式
*/
player.setScaleMode(2);
/**
* 设置最大缓冲时长,单位毫秒,只在软解时有效
*/
player.setBufferTime(2000);
player.on("start", () => {
//console.log("player on start");
removeLoader(".loader");
initControlStatus();
});
player.on("stop", () => {
console.log("player on stop");
});
player.on("error", (e) => {
console.log("player on error", e);
// 启动轮询
startPolling(streamUrl);
});
player.on("videoInfo", (w, h) => {
console.log("player on video info width=" + w + " height=" + h);
});
player.on("audioInfo", (r, c) => {
console.log("player on audio info samplerate=" + r + " channels=" + c);
});
player.on("stats", (stats) => {
// console.log("player on stats=", stats);
});
streamUrl && player.start(streamUrl);
player.setVolume(0);
observedPlayer.initialized = true; // 设置 player 已初始化的标志
// 初始化播放器状态同步
syncPlayerState();
});
}
function initVoice() {
NodePlayer.load(() => {
voicePlayer = new NodePlayer();
voicePlayer.setView("voice");
const streamUrl = getQueryString("voice_url");
streamUrl && voicePlayer.start(streamUrl);
voicePlayer.setVolume(1);
});
}
// #region 播放器事件
function startFunc(url) {
/**
* 开始播放,参数为 http-flv或 websocket-flv 的url
*/
player.start(url);
}
function stopFunc() {
/**
* 停止播放
*/
if (voice_url) {
voicePlayer.stop();
}
player.stop();
//按需清理画布为黑色背景
// player.clearView();
}
function volumeChange(value, type) {
/**
* 设置音量
* 0.0 ~~ 1.0
* 当为0.0时,完全静音, 最大1.0
*/
if (voice_url && type === "audio") {
voicePlayer.setVolume(value / 100.0);
} else {
player.setVolume(value / 100.0);
}
//console.info("player--:", player);
}
function screenshot() {
// player.screenshot("np_screenshot.png", "png");
player.screenshot("np_screenshot.jpeg", "jpeg", 0.8);
}
// #endregion
function handleMessage(m) {
if (m.e === "full") {
enterFullscreen();
} else if (m.e === "volume") {
volumeChange(m.v, "video");
}
}
/**
* @param {Event} event
* @returns
* @description 监听来自父页面的消息
* @example
* const data = {
* e: "volume",
* v: 75,
* };
* iframe?.contentWindow.postMessage(
* JSON.stringify(data),
* "https://test.vssm416.com"
* );
*/
// 在这里执行需要在页面完全加载后执行的代码
window.addEventListener("message", (event) => {
try {
if (typeof event.data !== "string") return;
console.log("iframe receive message", JSON.parse(event.data));
const data = JSON.parse(event.data);
if (isPlayerInitialized) {
handleMessage(data);
} else {
// 将消息加入队列
messageQueue.push(data);
}
} catch (error) {
console.error("iframe receive message error", error);
}
});
// #region 控件事件
function openVolume() {
console.log("openVolume ");
mute.style.display = "none";
trumpet.style.display = "block";
if (voice_url) {
voicePlayer.audioResume();
volumeChange(25, "video");
volumeChange(75, "audio");
} else {
volumeChange(75, "video");
}
player.audioResume();
console.log("openVolume", player, voicePlayer);
}
function closeVolume() {
// console.log('closeVolume ')
trumpet.style.display = "none";
mute.style.display = "block";
volumeChange(0, "video");
if (voice_url) {
volumeChange(0, "audio");
}
}
function reloadStream() {
location.reload();
}
onChangeVisibility && onChangeVisibility(stopFunc, reloadStream);
// window.addEventListener("click", function (e) {
// e.stopPropagation();
// });
// #endregion
document.addEventListener("DOMContentLoaded", function () {
initVideo();
if (voice_url) {
initVoice();
}
});
// ✅ 播放/暂停控制逻辑
var isPlaying = true;
var currentStreamUrl = null;
function togglePlayPause() {
const btn = document.getElementById("playPauseBtn");
if (!player) {
console.warn("Player not initialized");
return;
}
console.log("togglePlayPause isPlaying ", isPlaying);
if (isPlaying) {
// 暂停播放 - NodePlayer 使用 stop() 方法
try {
player.stop();
if (voicePlayer) {
voicePlayer.stop();
}
btn.src = "static/picture/play.svg";
isPlaying = false;
// 保存当前流地址用于恢复播放
currentStreamUrl = streamUrl;
console.log("Player paused");
// 通知父页面播放状态
window.parent.postMessage(
JSON.stringify({
type: "PLAYER_STATE",
state: "paused",
}),
"*"
);
closeVolume();
} catch (error) {
console.error("Error pausing player:", error);
}
} else {
// 恢复播放 - 重新启动流
try {
if (currentStreamUrl || streamUrl) {
const urlToPlay = currentStreamUrl || streamUrl;
player.start(urlToPlay);
if (voicePlayer && voice_url) {
voicePlayer.start(voice_url);
}
btn.src = "static/picture/pause.svg";
isPlaying = true;
console.log("Player resumed with URL:", urlToPlay);
// 通知父页面播放状态
window.parent.postMessage(
JSON.stringify({
type: "PLAYER_STATE",
state: "playing",
}),
"*"
);
} else {
console.warn("No stream URL available for resuming");
}
openVolume();
} catch (error) {
console.error("Error resuming player:", error);
}
}
}
// 监听播放器事件来同步状态
function syncPlayerState() {
if (player) {
player.on("start", () => {
console.log("Player started - updating state");
isPlaying = true;
const btn = document.getElementById("playPauseBtn");
if (btn) {
btn.src = "static/picture/pause.svg";
}
});
player.on("stop", () => {
console.log("Player stopped - updating state");
// 只有在手动暂停时才更新按钮状态,避免错误停止时的状态混乱
if (!isPlaying) {
const btn = document.getElementById("playPauseBtn");
if (btn) {
btn.src = "static/picture/play.svg";
}
}
});
}
}
// 点击播放器区域,通知父页面
function notifyParentPlayerClicked() {
const data = {
type: "PLAYER_EVENT",
action: "click",
};
// 发消息给上层 iframe 或页面
window.parent.postMessage(JSON.stringify(data), "*");
}
document.addEventListener("DOMContentLoaded", function () {
const overlay = document.getElementById("click-overlay");
if (overlay) {
overlay.addEventListener("click", function (e) {
console.log("overlay clicked!");
e.stopPropagation();
notifyParentPlayerClicked();
});
}
});
2025/12/23大约 5 分钟