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 { |
|||
/** |
@ -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