z_yu
2 years ago
35 changed files with 1136 additions and 85 deletions
-
679src/components/jsmpeg-player/jsmpeg-player.vue
-
0src/components/jsmpeg-player/jsmpeg/index.js
-
0src/components/jsmpeg-player/jsmpeg/modules/audio-output/index.js
-
0src/components/jsmpeg-player/jsmpeg/modules/audio-output/webaudio.js
-
24src/components/jsmpeg-player/jsmpeg/modules/buffer.js
-
7src/components/jsmpeg-player/jsmpeg/modules/decoder/decoder.js
-
0src/components/jsmpeg-player/jsmpeg/modules/decoder/index.js
-
0src/components/jsmpeg-player/jsmpeg/modules/decoder/mp2-wasm.js
-
3src/components/jsmpeg-player/jsmpeg/modules/decoder/mp2.js
-
0src/components/jsmpeg-player/jsmpeg/modules/decoder/mpeg1-wasm.js
-
7src/components/jsmpeg-player/jsmpeg/modules/decoder/mpeg1.js
-
0src/components/jsmpeg-player/jsmpeg/modules/demuxer/index.js
-
22src/components/jsmpeg-player/jsmpeg/modules/demuxer/ts.js
-
0src/components/jsmpeg-player/jsmpeg/modules/jsmpeg.js
-
30src/components/jsmpeg-player/jsmpeg/modules/player.js
-
0src/components/jsmpeg-player/jsmpeg/modules/renderer/canvas2d.js
-
0src/components/jsmpeg-player/jsmpeg/modules/renderer/index.js
-
0src/components/jsmpeg-player/jsmpeg/modules/renderer/webgl.js
-
0src/components/jsmpeg-player/jsmpeg/modules/source/ajax-progressive.js
-
0src/components/jsmpeg-player/jsmpeg/modules/source/ajax.js
-
0src/components/jsmpeg-player/jsmpeg/modules/source/fetch.js
-
0src/components/jsmpeg-player/jsmpeg/modules/source/index.js
-
8src/components/jsmpeg-player/jsmpeg/modules/source/websocket.js
-
4src/components/jsmpeg-player/jsmpeg/modules/video-element.js
-
5src/components/jsmpeg-player/jsmpeg/modules/wasm-module.js
-
0src/components/jsmpeg-player/jsmpeg/types/index.d.ts
-
6src/components/jsmpeg-player/jsmpeg/utils/index.js
-
70src/components/jsmpeg-player/styles/icon/iconfont.css
-
BINsrc/components/jsmpeg-player/styles/icon/iconfont.ttf
-
BINsrc/components/jsmpeg-player/styles/icon/iconfont.woff
-
BINsrc/components/jsmpeg-player/styles/icon/iconfont.woff2
-
19src/components/jsmpeg-player/styles/icon/index.css
-
183src/components/jsmpeg-player/styles/index.scss
-
100src/components/jsmpeg-player/styles/popover.scss
-
54src/views/storeManage/warehouse3D/module/video.vue
@ -0,0 +1,679 @@ |
|||||
|
<template> |
||||
|
<div class="jsmpeg-player" @mouseenter="handlePlayerMouseEnter" @mouseleave="handlePlayerMouseLeave"> |
||||
|
<div class="player-header" :class="{ 'is-show': showTitle }"> |
||||
|
<slot v-if="$slots.title" name="title" /> |
||||
|
<span v-else-if="displayTitle" class="player-title"> |
||||
|
{{ displayTitle }} |
||||
|
</span> |
||||
|
<div v-if="isRecording" class="recording-tips"> |
||||
|
<div class="recording-icon" :class="recordingDuration % 2 == 0 ? 'is-hide' : ''" /> |
||||
|
REC <template v-if="showTitle"> {{ recordingDurationLabel }} </template> |
||||
|
</div> |
||||
|
<button v-if="showCloseBtn" class="close-btn jm-icon-close" type="button" title="关闭" @click="$emit('close')" /> |
||||
|
</div> |
||||
|
<div ref="canvas-wrap" v-loading="loading" class="player-canvas__wrap" :element-loading-text="loadingText" @mousemove.passive="handleCanvasMouseMove" @click="handleCanvasClick"> |
||||
|
<!-- @dblclick="toggleFullscreen" --> |
||||
|
<!-- <canvas class="jsmpeg-canvas" |
||||
|
ref="canvas" /> --> |
||||
|
<template v-if="!loading && flags.noSignal"> |
||||
|
<template v-if="$slots['no-signal']"> |
||||
|
<slot name="no-signal" /> |
||||
|
</template> |
||||
|
<template v-else> |
||||
|
<div class="no-signal-text"> {{ noSignalText }} </div> |
||||
|
</template> |
||||
|
</template> |
||||
|
</div> |
||||
|
<div v-if="withToolbar" class="player-toolbar" :class="{ 'is-show': player && flags.playerHover }" @mouseenter="handleToolbarMouseEnter" @mouseleave="handleToolbarMouseLeave"> |
||||
|
<button class="toolbar-btn play-btn" type="button" :class="paused ? 'jm-icon-video-play is-paused' : 'jm-icon-video-pause'" :title="paused ? '播放' : '暂停'" @click="handleToolbar('play')" /> |
||||
|
<button class="toolbar-btn stop-btn jm-icon-stop" title="停止" type="button" @click="handleToolbar('stop')" /> |
||||
|
<button v-popover:popover-volume class="toolbar-btn volume-btn" type="button" title="音量" :class="isMuted ? 'jm-icon-muted' : 'jm-icon-volume'" @click="handleToolbar('mute')" /> |
||||
|
<div class="progress-bar"> |
||||
|
<span v-if="showDuration" class="current-time"> |
||||
|
{{ currentTimeLabel }} |
||||
|
</span> |
||||
|
</div> |
||||
|
<!-- <button class="snapshot-btn" |
||||
|
title="画中画" |
||||
|
@click="requesPip"> |
||||
|
<i class="jm-icon-copy-document"></i> |
||||
|
</button> --> |
||||
|
<button class="toolbar-btn snapshot-btn jm-icon-screenshots" title="截图" type="button" @click="handleToolbar('snapshot')" /> |
||||
|
<button class="toolbar-btn recording-btn jm-icon-recording" type="button" :class="isRecording ? 'is-recording' : ''" :title="isRecording ? '停止录制' : '录制'" @click="handleToolbar('recording')" /> |
||||
|
<button v-popover:popover-setting class="toolbar-btn setting-btn jm-icon-settings" title="设置" type="button" /> |
||||
|
<!-- <button |
||||
|
class="toolbar-btn fullscreen-btn" |
||||
|
type="button" |
||||
|
:class=" |
||||
|
flags.fullscreen ? 'jm-icon-exitfullscreen' : 'jm-icon-fullscreen' |
||||
|
" |
||||
|
:title="flags.fullscreen ? '取消全屏' : '全屏'" |
||||
|
@click="handleToolbar('fullscreen')" |
||||
|
></button> --> |
||||
|
</div> |
||||
|
<div class="overlayers"> |
||||
|
<template v-if="withToolbar"> |
||||
|
<el-popover ref="popover-setting" popper-class="jsmpeg-player-popover popover-setting" trigger="hover" placement="top-end" :visible-arrow="popoverVisibleArrow" :append-to-body="false"> |
||||
|
<!-- <div class="setting-item"> |
||||
|
<span class="label">禁用WebGL</span> |
||||
|
<div class="input__wrap"> |
||||
|
<el-switch class="input" |
||||
|
v-model="playerSettings.disableGl"> |
||||
|
</el-switch> |
||||
|
</div> |
||||
|
</div> --> |
||||
|
<!-- <div class="setting-item" |
||||
|
highlight> |
||||
|
<span class="label">后台播放</span> |
||||
|
<div class="input__wrap"> |
||||
|
<el-switch class="input" |
||||
|
v-model="playerSettings.backgroudPlay" |
||||
|
@change="settingPlayer('pauseWhenHidden',!$event)"> |
||||
|
</el-switch> |
||||
|
</div> |
||||
|
</div> --> |
||||
|
<div class="setting-item" highlight> |
||||
|
<span class="label">自动拉伸</span> |
||||
|
<div class="input__wrap"> |
||||
|
<el-switch v-model="playerSettings.autoStretch" class="input" @change="settingPlayer('autoStretch', $event)" /> |
||||
|
</div> |
||||
|
</div> |
||||
|
<div class="setting-item" highlight> |
||||
|
<span class="label">旋转画面</span> |
||||
|
<div class="input__wrap"> |
||||
|
<button class="toolbar-btn jm-icon-rotate-left" title="向左旋转90度" type="button" @click="rotate(-90, true)" /> |
||||
|
<button class="toolbar-btn jm-icon-rotate-right" title="向右旋转90度" type="button" @click="rotate(90, true)" /> |
||||
|
</div> |
||||
|
</div> |
||||
|
<!-- <div class="setting-item"> |
||||
|
<span class="label">test</span> |
||||
|
<div class="input__wrap"> |
||||
|
<el-button class="input" |
||||
|
@click="player.stop(true)"> |
||||
|
</el-button> |
||||
|
</div> |
||||
|
</div> --> |
||||
|
</el-popover> |
||||
|
<el-popover ref="popover-volume" popper-class="jsmpeg-player-popover popover-volume" trigger="hover" placement="top" :visible-arrow="popoverVisibleArrow" :append-to-body="false"> |
||||
|
<div class="volume-value">{{ volumePercent }}</div> |
||||
|
<el-slider |
||||
|
v-model="volume" |
||||
|
vertical |
||||
|
height="120px" |
||||
|
:max="1" |
||||
|
:min="0" |
||||
|
:step="0.01" |
||||
|
:show-tooltip="false" |
||||
|
:marks="{ |
||||
|
0: '', |
||||
|
0.5: '', |
||||
|
1: '' |
||||
|
}" |
||||
|
@change="$emit('volume-change', volume)" |
||||
|
/> |
||||
|
</el-popover> |
||||
|
</template> |
||||
|
</div> |
||||
|
</div> |
||||
|
</template> |
||||
|
|
||||
|
<script> |
||||
|
import JSMpeg from './jsmpeg' |
||||
|
// import fullscreen from '@/utils/fullscreen' |
||||
|
|
||||
|
/** 补零 */ |
||||
|
function prefixPadZero(num) { |
||||
|
return (num >= 10 ? '' : '0') + num |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* 将秒转换为时:分:秒 |
||||
|
*/ |
||||
|
function formatTime(time) { |
||||
|
const seconds = parseInt(time % 60) |
||||
|
const minutes = parseInt(time / 60) |
||||
|
const hours = parseInt(minutes / 60) |
||||
|
return time < 3600 |
||||
|
? `${prefixPadZero(minutes)}:${prefixPadZero(seconds)}` |
||||
|
: `${prefixPadZero(hours)}:${prefixPadZero(minutes)}:${prefixPadZero( |
||||
|
seconds |
||||
|
)}` |
||||
|
} |
||||
|
|
||||
|
const defaultOptions = () => ({ |
||||
|
/** 是否循环播放视频(仅静态文件)。默认true */ |
||||
|
autoplay: true, |
||||
|
/** 是否解码音频。默认true */ |
||||
|
audio: true, |
||||
|
/** 是否解码视频。默认true */ |
||||
|
video: true, |
||||
|
/** 预览图像的URL,用来在视频播放之前作为海报显示。 */ |
||||
|
poster: null, |
||||
|
/** 是否禁用后台播放,当web页面处于非活动状态时是否暂停播放,默认true(注意,浏览器通常会在非活动标签中限制JS) */ |
||||
|
pauseWhenHidden: true, |
||||
|
/** 是否禁用WebGL,始终使用Canvas2D渲染器。默认.false */ |
||||
|
disableGl: false, |
||||
|
/** 是否禁用WebAssembly并始终使用JavaScript解码器。默认false */ |
||||
|
disableWebAssembly: false, |
||||
|
/** WebGL上下文是否创建必要的“截图”。默认false */ |
||||
|
preserveDrawingBuffer: true, |
||||
|
/** 是否以块的形式加载数据(仅静态文件)。当启用时,回放可以在完整加载源之前开始,默认=true */ |
||||
|
progressive: true, |
||||
|
/** 当不需要回放时是否推迟加载块。默认=progressive */ |
||||
|
throttled: true, |
||||
|
/** 使用时,以字节为单位加载的块大小。默认(1 mb)1024*1024 */ |
||||
|
chunkSize: 1024 * 1024, |
||||
|
/** 是否解码并显示视频的第一帧,一般用于设置画布大小以及使用初始帧作为"poster"图像。当使用自动播放或流媒体资源时,此参数不受影响。默认true */ |
||||
|
decodeFirstFrame: false, |
||||
|
/** 流媒体时,以秒为单位的最大排队音频长度。(可以理解为能接受的最大音画不同步时间) */ |
||||
|
maxAudioLag: 0.25, |
||||
|
/** 流媒体时,视频解码缓冲区的字节大小。默认的512 * 1024 (512 kb)。对于非常高的比特率,您可能需要增加此值。 */ |
||||
|
videoBufferSize: 1024 * 1024, |
||||
|
/** 流媒体时,音频解码缓冲区的字节大小。默认的128 * 1024 (128 kb)。对于非常高的比特率,您可能需要增加此值。 */ |
||||
|
audioBufferSize: 256 * 1024 |
||||
|
}) |
||||
|
|
||||
|
export default { |
||||
|
components: {}, |
||||
|
inheritAttrs: false, |
||||
|
|
||||
|
// #region 组件基础 |
||||
|
props: { |
||||
|
url: { |
||||
|
type: String, |
||||
|
default: '' |
||||
|
}, |
||||
|
title: { |
||||
|
type: String, |
||||
|
default: '' |
||||
|
}, |
||||
|
options: { |
||||
|
type: Object, |
||||
|
default: defaultOptions |
||||
|
}, |
||||
|
/** 是否可关闭(单击关闭按钮,仅抛出事件) */ |
||||
|
closeable: Boolean, |
||||
|
/** 是否处于后台,如el-tabs的切换,路由的切换等 */ |
||||
|
inBackground: Boolean, |
||||
|
/** 是否现实持续播放时间 */ |
||||
|
showDuration: { |
||||
|
type: Boolean, |
||||
|
default: true |
||||
|
}, |
||||
|
/** 默认静音 */ |
||||
|
defaultMute: { |
||||
|
type: Boolean, |
||||
|
default: true |
||||
|
}, |
||||
|
/** 是否需要工具栏 */ |
||||
|
withToolbar: { |
||||
|
type: Boolean, |
||||
|
default: true |
||||
|
}, |
||||
|
popoverVisibleArrow: { |
||||
|
type: Boolean, |
||||
|
default: true |
||||
|
}, |
||||
|
noSignalText: { |
||||
|
type: String, |
||||
|
default: '无信号' |
||||
|
}, |
||||
|
loadingText: { |
||||
|
type: String, |
||||
|
default: '拼命加载中...' |
||||
|
} |
||||
|
}, |
||||
|
inject: { |
||||
|
/** @returns {any} */ |
||||
|
rootTabs: { |
||||
|
default: '' |
||||
|
} |
||||
|
}, |
||||
|
// #endregion |
||||
|
|
||||
|
// #region 数据相关 |
||||
|
data() { |
||||
|
return { |
||||
|
loading: false, |
||||
|
/** @type {import('./jsmpeg/types').JSMpegPlayer} */ |
||||
|
player: null, |
||||
|
lastVolume: 0, |
||||
|
flags: { |
||||
|
/** |
||||
|
* 是否处于无信号状态 |
||||
|
* 1.当流中断事件触发后,15秒后还没有收到ws消息 |
||||
|
* 2.ws关闭事件触发 |
||||
|
*/ |
||||
|
noSignal: false, |
||||
|
/** 是否已获取到视频分辨率 */ |
||||
|
gotResolution: false, |
||||
|
/** 是否鼠标悬停在播放器内部 */ |
||||
|
playerHover: false, |
||||
|
/** 是否处于全屏播放 */ |
||||
|
fullscreen: false |
||||
|
}, |
||||
|
playerSettings: { |
||||
|
disableGl: false, |
||||
|
/** canvas旋转角度 */ |
||||
|
rotationAngle: 0, |
||||
|
/** 后台播放 */ |
||||
|
backgroudPlay: false, |
||||
|
/** 自动拉伸 */ |
||||
|
autoStretch: false |
||||
|
}, |
||||
|
timers: { |
||||
|
noSignal: null, |
||||
|
canvasMouseMove: null |
||||
|
} |
||||
|
} |
||||
|
}, |
||||
|
computed: { |
||||
|
/** @returns {string} */ |
||||
|
displayTitle() { |
||||
|
return this.title || this.url |
||||
|
}, |
||||
|
/** @returns {boolean} */ |
||||
|
paused() { |
||||
|
return this.player?.paused ?? true |
||||
|
}, |
||||
|
/** @returns {number} */ |
||||
|
volume: { |
||||
|
/** @returns {number} */ |
||||
|
set(val) { |
||||
|
if (!this.player) return |
||||
|
|
||||
|
if (val >= 1) { |
||||
|
this.player.volume = 1 |
||||
|
} else if (val <= 0) { |
||||
|
this.player.volume = 0 |
||||
|
} else { |
||||
|
this.player.volume = val |
||||
|
} |
||||
|
|
||||
|
if (this.player.volume === 0) { |
||||
|
this.$emit('muted', this.player.volume) |
||||
|
} |
||||
|
}, |
||||
|
/** @returns {number} */ |
||||
|
get() { |
||||
|
return this.player?.volume ?? 100 |
||||
|
} |
||||
|
}, |
||||
|
/** @returns {number} */ |
||||
|
volumePercent() { |
||||
|
return parseInt(this.volume * 100) |
||||
|
}, |
||||
|
/** @returns {number} */ |
||||
|
currentTime: { |
||||
|
set(val) { |
||||
|
this.player.currentTime = val |
||||
|
}, |
||||
|
get() { |
||||
|
return this.player?.currentTime ?? 0 |
||||
|
} |
||||
|
}, |
||||
|
/** @returns {string} */ |
||||
|
currentTimeLabel() { |
||||
|
return formatTime(this.currentTime) |
||||
|
}, |
||||
|
/** @returns {boolean} */ |
||||
|
isMuted() { |
||||
|
return this.volume === 0 |
||||
|
}, |
||||
|
/** @returns {boolean} */ |
||||
|
isRecording() { |
||||
|
return this.player && this.player.isRecording |
||||
|
}, |
||||
|
/** @returns {number} */ |
||||
|
recordingDuration() { |
||||
|
return this.player ? this.player.recordingDuration : 0 |
||||
|
}, |
||||
|
/** @returns {string} */ |
||||
|
recordingDurationLabel() { |
||||
|
return formatTime(this.recordingDuration) |
||||
|
}, |
||||
|
/** @returns {boolean} */ |
||||
|
showCloseBtn() { |
||||
|
return this.closeable && !this.flags.fullscreen |
||||
|
}, |
||||
|
/** @returns {boolean} */ |
||||
|
showTitle() { |
||||
|
return this.flags.playerHover |
||||
|
// && |
||||
|
// (this.$slots.title || this.title || this.showCloseBtn) |
||||
|
} |
||||
|
}, |
||||
|
watch: { |
||||
|
url(nval) { |
||||
|
// this.rotate(0) |
||||
|
// if (this.player) { |
||||
|
// this.player.setUrl(nval) |
||||
|
// } else { |
||||
|
// this.initPlayer() |
||||
|
// } |
||||
|
this.player?.destroy() |
||||
|
|
||||
|
if (this.url == null || this.url === '') { |
||||
|
this.player = null |
||||
|
} else { |
||||
|
this.initPlayer() |
||||
|
} |
||||
|
}, |
||||
|
options: { |
||||
|
deep: true, |
||||
|
handler() { |
||||
|
this.destroyPlayer() |
||||
|
this.initPlayer() |
||||
|
} |
||||
|
}, |
||||
|
inBackground(nval) { |
||||
|
if (nval) { |
||||
|
this.intoBackground() |
||||
|
} else { |
||||
|
this.intoFront() |
||||
|
} |
||||
|
} |
||||
|
}, |
||||
|
// #endregion |
||||
|
|
||||
|
// #region 生命周期 |
||||
|
mounted() { |
||||
|
if (this.rootTabs) { |
||||
|
this.rootTabs.$on('tab-click', (tab) => { |
||||
|
try { |
||||
|
// 处理el-tabs切换标签时,el-table右侧可能出现空白的Bug |
||||
|
if (!tab.$el?.contains(this.$el)) { |
||||
|
this.intoBackground() |
||||
|
this.$emit('update:inBackground', true) |
||||
|
} |
||||
|
} catch (error) { |
||||
|
console.log(error) |
||||
|
} |
||||
|
}) |
||||
|
} |
||||
|
window.addEventListener('unload', () => { |
||||
|
this.destroyPlayer() |
||||
|
}) |
||||
|
this.init() |
||||
|
}, |
||||
|
beforeDestroy() { |
||||
|
this.destroyPlayer() |
||||
|
}, |
||||
|
// #endregion |
||||
|
|
||||
|
methods: { |
||||
|
init() { |
||||
|
this.initPlayer() |
||||
|
}, |
||||
|
|
||||
|
initPlayer() { |
||||
|
if (!this.url) return |
||||
|
|
||||
|
this.loading = true |
||||
|
this.player = new JSMpeg.Player(this.url, { |
||||
|
contianer: this.$refs['canvas-wrap'], |
||||
|
...this.options, |
||||
|
onVideoDecode: (decoder, time) => { |
||||
|
this.$emit('video-decode', decoder, time) |
||||
|
}, |
||||
|
onAudioDecode: (decoder, time) => { |
||||
|
this.$emit('audio-decode', decoder, time) |
||||
|
}, |
||||
|
onPlay: (player) => { |
||||
|
this.loading = false |
||||
|
console.log('onPlay') |
||||
|
this.$emit('play', player) |
||||
|
}, |
||||
|
onPause: (player) => { |
||||
|
this.loading = false |
||||
|
console.log('onPause') |
||||
|
this.$emit('pause', player) |
||||
|
}, |
||||
|
onEnded: (player) => { |
||||
|
console.log('onEnded') |
||||
|
this.$emit('ended', player) |
||||
|
}, |
||||
|
onStalled: (player) => { |
||||
|
console.log('onStalled') |
||||
|
this.$emit('stalled', player) |
||||
|
}, |
||||
|
onSourceEstablished: (source) => { |
||||
|
console.log('onSourceEstablished') |
||||
|
|
||||
|
this.flags.noSignal = false |
||||
|
this.loading = false |
||||
|
clearTimeout(this.timers.noSignal) |
||||
|
this.timers.noSignal = null |
||||
|
|
||||
|
this.$emit('source-established', source) |
||||
|
}, |
||||
|
onSourceCompleted: (source) => { |
||||
|
console.log('onSourceCompleted') |
||||
|
this.$emit('source-completed', source) |
||||
|
}, |
||||
|
onSourceConnected: () => { |
||||
|
console.log('onSourceConnected') |
||||
|
clearTimeout(this.timers.noSignal) |
||||
|
this.loading = true |
||||
|
this.flags.noSignal = false |
||||
|
this.$emit('source-connected') |
||||
|
}, |
||||
|
onSourceStreamInterrupt: () => { |
||||
|
console.log('onSourceStreamInterrupt') |
||||
|
this.loading = true |
||||
|
clearTimeout(this.timers.noSignal) |
||||
|
|
||||
|
this.timers.noSignal = setTimeout(this.handleNoSignal, 15000) |
||||
|
this.$emit('source-interrupt') |
||||
|
}, |
||||
|
onSourceStreamContinue: () => { |
||||
|
console.log('onSourceStreamContinue') |
||||
|
clearTimeout(this.timers.noSignal) |
||||
|
this.timers.noSignal = null |
||||
|
this.loading = false |
||||
|
this.flags.noSignal = false |
||||
|
this.$emit('source-continue') |
||||
|
}, |
||||
|
onSourceClosed: () => { |
||||
|
console.log('onSourceClosed') |
||||
|
clearTimeout(this.timers.noSignal) |
||||
|
this.$emit('source-closed') |
||||
|
this.handleNoSignal() |
||||
|
}, |
||||
|
onResolutionDecode: (width, height) => { |
||||
|
// 从流中获取到视频的分辨率 |
||||
|
this.flags.gotResolution = true |
||||
|
this.settingPlayer('autoStretch', this.playerSettings.autoStretch) |
||||
|
this.$emit('resolution-decode', width, height) |
||||
|
} |
||||
|
}) |
||||
|
this.playerSettings.backgroudPlay = !this.options.pauseWhenHidden |
||||
|
|
||||
|
if (this.defaultMute) { |
||||
|
this.volume = 0 |
||||
|
} |
||||
|
|
||||
|
this.timers.noSignal = setTimeout(this.handleNoSignal, 15000) |
||||
|
|
||||
|
for (const key in this.playerSettings) { |
||||
|
this.settingPlayer(key, this.playerSettings[key]) |
||||
|
} |
||||
|
|
||||
|
console.log('player', this.player) |
||||
|
}, |
||||
|
rotate(angle, append = false) { |
||||
|
this.player.rotate(angle, append) |
||||
|
}, |
||||
|
|
||||
|
// #region 方法 |
||||
|
/** |
||||
|
* 进入画中画模式 |
||||
|
* @deprecated 未实现 |
||||
|
*/ |
||||
|
requesPip() { |
||||
|
// if (!document.pictureInPictureElement) { |
||||
|
// this.$refs.canvas.requestPictureInPicture() |
||||
|
// } |
||||
|
}, |
||||
|
/** |
||||
|
* 退出画中画模式 |
||||
|
* @deprecated 未实现 |
||||
|
*/ |
||||
|
exitPip() { |
||||
|
// document.exitPictureInPicture() |
||||
|
}, |
||||
|
/** |
||||
|
* 切换全屏模式 |
||||
|
*/ |
||||
|
// toggleFullscreen() { |
||||
|
// if (this.flags.fullscreen) { |
||||
|
// fullscreen.exit(this.$el) |
||||
|
// } else { |
||||
|
// fullscreen.request(this.$el, () => { |
||||
|
// this.flags.fullscreen = false |
||||
|
// }) |
||||
|
// } |
||||
|
// this.flags.fullscreen = !this.flags.fullscreen |
||||
|
// }, |
||||
|
play() { |
||||
|
if (!this.url) return |
||||
|
this.loading = true |
||||
|
if (!this.player) { |
||||
|
this.initPlayer() |
||||
|
} |
||||
|
this.player?.play() |
||||
|
}, |
||||
|
/** |
||||
|
* 切换播放模式 |
||||
|
*/ |
||||
|
togglePlay() { |
||||
|
if (this.paused) { |
||||
|
this.play() |
||||
|
} else { |
||||
|
this.pause() |
||||
|
} |
||||
|
}, |
||||
|
pause() { |
||||
|
this.player?.pause() |
||||
|
}, |
||||
|
intoFront() { |
||||
|
this.player?.intoFront() |
||||
|
}, |
||||
|
intoBackground() { |
||||
|
this.player?.intoBackground() |
||||
|
}, |
||||
|
stop(clear) { |
||||
|
this.player?.stop(clear) |
||||
|
}, |
||||
|
nextFrame() { |
||||
|
this.player?.nextFrame() |
||||
|
}, |
||||
|
destroyPlayer() { |
||||
|
this.stop() |
||||
|
this.player?.destroy() |
||||
|
this.player = null |
||||
|
}, |
||||
|
mute() { |
||||
|
this.lastVolume = this.volume |
||||
|
this.volume = 0 |
||||
|
}, |
||||
|
toggleMute() { |
||||
|
if (this.isMuted) { |
||||
|
this.volume = this.lastVolume ? this.lastVolume : 1 |
||||
|
} else { |
||||
|
this.mute() |
||||
|
} |
||||
|
this.$emit('volume-change', this.volume) |
||||
|
}, |
||||
|
/** 截图 */ |
||||
|
snapshot() { |
||||
|
this.player?.snapshot(this.displayTitle) |
||||
|
}, |
||||
|
recording() { |
||||
|
this.player?.recording(this.title) |
||||
|
}, |
||||
|
|
||||
|
/** |
||||
|
* @param |
||||
|
*/ |
||||
|
settingPlayer(optionName, value) { |
||||
|
if (!this.player) return |
||||
|
switch (optionName) { |
||||
|
case 'autoStretch': { |
||||
|
if (!this.flags.gotResolution) return |
||||
|
|
||||
|
const canvas = this.player.canvas |
||||
|
if (value) { |
||||
|
if (canvas.width > canvas.height) { |
||||
|
canvas.style.width = '100%' |
||||
|
} else { |
||||
|
canvas.style.height = '100%' |
||||
|
} |
||||
|
} else { |
||||
|
canvas.style.width = '' |
||||
|
canvas.style.height = '' |
||||
|
} |
||||
|
break |
||||
|
} |
||||
|
default: |
||||
|
this.player?.setOption(optionName, value) |
||||
|
break |
||||
|
} |
||||
|
}, |
||||
|
// #endregion |
||||
|
|
||||
|
handleToolbar(cmd) { |
||||
|
if (!this.player) return |
||||
|
|
||||
|
switch (cmd) { |
||||
|
case 'play': |
||||
|
this.togglePlay() |
||||
|
break |
||||
|
case 'stop': |
||||
|
this.stop() |
||||
|
break |
||||
|
case 'mute': |
||||
|
this.toggleMute() |
||||
|
break |
||||
|
case 'snapshot': |
||||
|
this.snapshot() |
||||
|
break |
||||
|
case 'recording': |
||||
|
this.recording() |
||||
|
break |
||||
|
// case 'fullscreen': |
||||
|
// this.toggleFullscreen() |
||||
|
// break |
||||
|
} |
||||
|
}, |
||||
|
handleNoSignal() { |
||||
|
this.flags.noSignal = true |
||||
|
this.loading = false |
||||
|
this.stop() |
||||
|
this.$emit('no-signal') |
||||
|
}, |
||||
|
handlePlayerMouseEnter() { |
||||
|
this.flags.playerHover = true |
||||
|
}, |
||||
|
handleCanvasMouseMove() { |
||||
|
this.flags.playerHover = true |
||||
|
clearTimeout(this.timers.canvasMouseMove) |
||||
|
this.timers.canvasMouseMove = setTimeout(() => { |
||||
|
this.flags.playerHover = false |
||||
|
}, 3000) |
||||
|
}, |
||||
|
handlePlayerMouseLeave() { |
||||
|
clearTimeout(this.timers.canvasMouseMove) |
||||
|
this.flags.playerHover = false |
||||
|
}, |
||||
|
handleCanvasClick() { }, |
||||
|
handleToolbarMouseEnter() { |
||||
|
this.flags.playerHover = true |
||||
|
clearTimeout(this.timers.canvasMouseMove) |
||||
|
}, |
||||
|
handleToolbarMouseLeave() { } |
||||
|
} |
||||
|
} |
||||
|
</script> |
||||
|
|
||||
|
<style lang="scss" src="./styles/index.scss"></style> |
@ -1,7 +1,6 @@ |
|||||
/* eslint-disable */ |
|
||||
import WebAudioOut from '../audio-output/webaudio' |
|
||||
import CanvasRenderer from '../renderer/canvas2d' |
|
||||
import WebGLRenderer from '../renderer/webgl' |
|
||||
|
// import WebAudioOut from '../audio-output/webaudio'
|
||||
|
// import CanvasRenderer from '../renderer/canvas2d'
|
||||
|
// import WebGLRenderer from '../renderer/webgl'
|
||||
|
|
||||
export default class BaseDecoder { |
export default class BaseDecoder { |
||||
/** |
/** |
@ -0,0 +1,70 @@ |
|||||
|
@font-face { |
||||
|
font-family: 'jsmpeg-player'; /* Project id 2580924 */ |
||||
|
src: url('iconfont.woff2?t=1660312070074') format('woff2'), |
||||
|
url('iconfont.woff?t=1660312070074') format('woff'), |
||||
|
url('iconfont.ttf?t=1660312070074') format('truetype'); |
||||
|
} |
||||
|
|
||||
|
.jsmpeg-player { |
||||
|
font-family: 'jsmpeg-player' !important; |
||||
|
font-size: 16px; |
||||
|
font-style: normal; |
||||
|
-webkit-font-smoothing: antialiased; |
||||
|
-moz-osx-font-smoothing: grayscale; |
||||
|
} |
||||
|
|
||||
|
.jm-icon-close:before { |
||||
|
content: '\e661'; |
||||
|
} |
||||
|
|
||||
|
.jm-icon-settings:before { |
||||
|
content: '\e892'; |
||||
|
} |
||||
|
|
||||
|
.jm-icon-video-play:before { |
||||
|
content: '\e600'; |
||||
|
} |
||||
|
|
||||
|
.jm-icon-more:before { |
||||
|
content: '\e601'; |
||||
|
} |
||||
|
|
||||
|
.jm-icon-screenshots:before { |
||||
|
content: '\e602'; |
||||
|
} |
||||
|
|
||||
|
.jm-icon-video-pause:before { |
||||
|
content: '\e603'; |
||||
|
} |
||||
|
|
||||
|
.jm-icon-recording:before { |
||||
|
content: '\e663'; |
||||
|
} |
||||
|
|
||||
|
.jm-icon-rotate-right:before { |
||||
|
content: '\e698'; |
||||
|
} |
||||
|
|
||||
|
.jm-icon-rotate-left:before { |
||||
|
content: '\e699'; |
||||
|
} |
||||
|
|
||||
|
.jm-icon-stop:before { |
||||
|
content: '\e611'; |
||||
|
} |
||||
|
|
||||
|
.jm-icon-fullscreen-exit:before { |
||||
|
content: '\e65d'; |
||||
|
} |
||||
|
|
||||
|
.jm-icon-fullscreen:before { |
||||
|
content: '\e65e'; |
||||
|
} |
||||
|
|
||||
|
.jm-icon-muted:before { |
||||
|
content: '\e62d'; |
||||
|
} |
||||
|
|
||||
|
.jm-icon-volume:before { |
||||
|
content: '\e62e'; |
||||
|
} |
@ -0,0 +1,19 @@ |
|||||
|
@import './iconfont.css'; |
||||
|
|
||||
|
/* * [class*='jm-icon-'] + span { |
||||
|
margin-left: 5px; |
||||
|
} */ |
||||
|
|
||||
|
[class*='jm-icon-'], |
||||
|
[class^='jm-icon-'] { |
||||
|
font-family: 'jsmpeg-player' !important; |
||||
|
speak: none; |
||||
|
font-style: normal; |
||||
|
font-weight: normal; |
||||
|
font-variant: normal; |
||||
|
text-transform: none; |
||||
|
line-height: 1; |
||||
|
vertical-align: baseline; |
||||
|
display: inline-block; |
||||
|
-webkit-font-smoothing: antialiased; |
||||
|
} |
@ -0,0 +1,183 @@ |
|||||
|
@import './icon/index.css'; |
||||
|
@import './popover.scss'; |
||||
|
|
||||
|
.jsmpeg-player { |
||||
|
width: 100%; |
||||
|
height: 100%; |
||||
|
position: relative; |
||||
|
overflow: hidden; |
||||
|
display: flex; |
||||
|
background-color: #000; |
||||
|
button { |
||||
|
background: none; |
||||
|
border: none; |
||||
|
display: flex; |
||||
|
font-size: inherit; |
||||
|
line-height: inherit; |
||||
|
text-transform: none; |
||||
|
text-decoration: none; |
||||
|
cursor: pointer; |
||||
|
overflow: hidden; |
||||
|
} |
||||
|
.player-header { |
||||
|
width: 100%; |
||||
|
height: 40px; |
||||
|
line-height: 40px; |
||||
|
position: absolute; |
||||
|
top: 0; |
||||
|
left: 0; |
||||
|
padding: 0 10px; |
||||
|
background: linear-gradient(#000, transparent); |
||||
|
transform: translateY(-100%); |
||||
|
transition: 0.48s transform ease-in-out; |
||||
|
z-index: 10; |
||||
|
&.is-show { |
||||
|
transform: translateY(0); |
||||
|
.recording-tips { |
||||
|
display: inline-flex; |
||||
|
transform: translateY(0) !important; |
||||
|
// transition: 0.45s display; |
||||
|
} |
||||
|
} |
||||
|
.player-title { |
||||
|
color: #fff; |
||||
|
float: left; |
||||
|
} |
||||
|
.recording-tips { |
||||
|
height: 40px; |
||||
|
font-size: 14px; |
||||
|
color: white; |
||||
|
float: right; |
||||
|
display: flex; |
||||
|
flex-direction: row; |
||||
|
justify-content: flex-end; |
||||
|
align-items: center; |
||||
|
transform: translateY(100%); |
||||
|
transition: transform 0.28s; |
||||
|
.recording-icon { |
||||
|
width: 10px; |
||||
|
height: 10px; |
||||
|
background-color: red; |
||||
|
border-radius: 5px; |
||||
|
margin-left: 8px; |
||||
|
margin-right: 6px; |
||||
|
transition: 0.25s background-color ease-in; |
||||
|
&.is-hide { |
||||
|
background-color: transparent; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
.close-btn { |
||||
|
color: gray; |
||||
|
transition: 0.28s color; |
||||
|
position: absolute; |
||||
|
top: 0; |
||||
|
right: 5px; |
||||
|
font-size: 18px; |
||||
|
&:hover { |
||||
|
color: #f56c6c; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
.player-canvas__wrap { |
||||
|
width: 100%; |
||||
|
height: 100%; |
||||
|
display: flex; |
||||
|
justify-content: center; |
||||
|
align-items: center; |
||||
|
z-index: 1; |
||||
|
canvas { |
||||
|
max-width: 100%; |
||||
|
max-height: 100%; |
||||
|
// transition: 0.28s transform; |
||||
|
} |
||||
|
.no-signal-text { |
||||
|
width: 100%; |
||||
|
height: 100%; |
||||
|
display: flex; |
||||
|
justify-content: center; |
||||
|
align-items: center; |
||||
|
color: white; |
||||
|
position: absolute; |
||||
|
top: 0; |
||||
|
left: 0; |
||||
|
background-color: #000; |
||||
|
} |
||||
|
.el-loading-mask { |
||||
|
background-color: transparent; |
||||
|
} |
||||
|
} |
||||
|
.player-toolbar { |
||||
|
width: 100%; |
||||
|
height: 45px; |
||||
|
line-height: 36px; |
||||
|
background: linear-gradient(transparent, #000); |
||||
|
padding: 0 8px; |
||||
|
position: absolute; |
||||
|
bottom: 0px; |
||||
|
left: 0px; |
||||
|
display: flex; |
||||
|
flex-direction: row; |
||||
|
align-items: center; |
||||
|
transform: translateY(100%); |
||||
|
transition: 0.48s transform ease-in-out; |
||||
|
z-index: 10; |
||||
|
&.is-show { |
||||
|
transform: translateY(0); |
||||
|
} |
||||
|
.toolbar-btn-container { |
||||
|
height: 35px; |
||||
|
width: 35px; |
||||
|
line-height: 1; |
||||
|
} |
||||
|
.toolbar-btn { |
||||
|
color: whitesmoke !important; |
||||
|
opacity: 0.8; |
||||
|
transition: 0.28s opacity ease-in-out, 0.28s color; |
||||
|
&:hover { |
||||
|
opacity: 1; |
||||
|
} |
||||
|
img.icon { |
||||
|
object-fit: scale-down; |
||||
|
max-width: 100%; |
||||
|
max-height: 100%; |
||||
|
} |
||||
|
} |
||||
|
> .toolbar-btn { |
||||
|
max-height: 35px; |
||||
|
max-width: 35px; |
||||
|
font-size: 24px; |
||||
|
} |
||||
|
.play-btn { |
||||
|
transition: 0.28s color; |
||||
|
// &:hover { |
||||
|
// color: #409eff !important; |
||||
|
// } |
||||
|
// color: #f56c6c !important; |
||||
|
// &.is-paused { |
||||
|
// color: #409eff !important; |
||||
|
// } |
||||
|
} |
||||
|
.recording-btn { |
||||
|
&.is-recording { |
||||
|
color: #f56c6c !important; |
||||
|
} |
||||
|
} |
||||
|
.stop-btn { |
||||
|
color: #f56c6c !important; |
||||
|
} |
||||
|
.progress-bar { |
||||
|
flex: 1; |
||||
|
padding: 0 10px; |
||||
|
.current-time { |
||||
|
float: right; |
||||
|
cursor: default; |
||||
|
color: whitesmoke !important; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
.overlayers { |
||||
|
width: 0; |
||||
|
height: 0; |
||||
|
} |
||||
|
} |
@ -0,0 +1,100 @@ |
|||||
|
.jsmpeg-player-popover { |
||||
|
$background: rgba( |
||||
|
$color: dimgray, |
||||
|
$alpha: 0.8 |
||||
|
); |
||||
|
|
||||
|
border: none !important; |
||||
|
padding: 16px 8px; |
||||
|
min-width: 0 !important; |
||||
|
background-color: $background !important; |
||||
|
.popper__arrow { |
||||
|
&::after { |
||||
|
border-top-color: $background !important; |
||||
|
border-radius: 0; |
||||
|
} |
||||
|
} |
||||
|
&.popover-volume { |
||||
|
.volume-value { |
||||
|
font-size: 12px; |
||||
|
text-align: center; |
||||
|
color: white; |
||||
|
} |
||||
|
.el-slider { |
||||
|
margin-top: 10px; |
||||
|
.el-slider__runway { |
||||
|
background: dimgray; |
||||
|
} |
||||
|
.el-slider__bar { |
||||
|
// background: lightgray; |
||||
|
} |
||||
|
.el-slider__marks-text { |
||||
|
color: white !important; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
&.popover-setting { |
||||
|
display: flex; |
||||
|
flex-direction: column; |
||||
|
padding: 8px 0; |
||||
|
.setting-item { |
||||
|
color: whitesmoke; |
||||
|
cursor: pointer; |
||||
|
padding: 8px 15px; |
||||
|
// margin: 0 15px; |
||||
|
transition: 0.28s color; |
||||
|
height: 34px; |
||||
|
display: flex; |
||||
|
flex-direction: row; |
||||
|
align-items: center; |
||||
|
|
||||
|
&[highlight]:hover { |
||||
|
color: #409eff; |
||||
|
// background-color: #409eff; |
||||
|
} |
||||
|
& + .setting-item { |
||||
|
// border-top: 1px solid lightgray; |
||||
|
} |
||||
|
.label { |
||||
|
text-align: right; |
||||
|
// flex: 1; |
||||
|
width: 80px; |
||||
|
// font-weight: 700; |
||||
|
} |
||||
|
> .input__wrap, |
||||
|
> .icon { |
||||
|
margin: 0 10px; |
||||
|
max-width: 100px; |
||||
|
} |
||||
|
.input__wrap { |
||||
|
display: flex; |
||||
|
flex-direction: row; |
||||
|
align-items: center; |
||||
|
> * { |
||||
|
background-color: transparent; |
||||
|
color: whitesmoke; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
.el-switch { |
||||
|
width: 30px; |
||||
|
&.is-checked { |
||||
|
.el-switch__core::after { |
||||
|
margin-left: -14px !important; |
||||
|
} |
||||
|
} |
||||
|
.el-switch__core { |
||||
|
height: 16px; |
||||
|
width: 100%; |
||||
|
// height: 15px; |
||||
|
margin: 0; |
||||
|
position: relative; |
||||
|
&::after { |
||||
|
height: 12px; |
||||
|
width: 12px; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
Write
Preview
Loading…
Cancel
Save
Reference in new issue