Browse Source

视频实时播放jsmpeg->jsmpeg-player

master
z_yu 2 years ago
parent
commit
bfe6c6b71c
  1. 679
      src/components/jsmpeg-player/jsmpeg-player.vue
  2. 0
      src/components/jsmpeg-player/jsmpeg/index.js
  3. 0
      src/components/jsmpeg-player/jsmpeg/modules/audio-output/index.js
  4. 0
      src/components/jsmpeg-player/jsmpeg/modules/audio-output/webaudio.js
  5. 24
      src/components/jsmpeg-player/jsmpeg/modules/buffer.js
  6. 7
      src/components/jsmpeg-player/jsmpeg/modules/decoder/decoder.js
  7. 0
      src/components/jsmpeg-player/jsmpeg/modules/decoder/index.js
  8. 0
      src/components/jsmpeg-player/jsmpeg/modules/decoder/mp2-wasm.js
  9. 3
      src/components/jsmpeg-player/jsmpeg/modules/decoder/mp2.js
  10. 0
      src/components/jsmpeg-player/jsmpeg/modules/decoder/mpeg1-wasm.js
  11. 7
      src/components/jsmpeg-player/jsmpeg/modules/decoder/mpeg1.js
  12. 0
      src/components/jsmpeg-player/jsmpeg/modules/demuxer/index.js
  13. 22
      src/components/jsmpeg-player/jsmpeg/modules/demuxer/ts.js
  14. 0
      src/components/jsmpeg-player/jsmpeg/modules/jsmpeg.js
  15. 30
      src/components/jsmpeg-player/jsmpeg/modules/player.js
  16. 0
      src/components/jsmpeg-player/jsmpeg/modules/renderer/canvas2d.js
  17. 0
      src/components/jsmpeg-player/jsmpeg/modules/renderer/index.js
  18. 0
      src/components/jsmpeg-player/jsmpeg/modules/renderer/webgl.js
  19. 0
      src/components/jsmpeg-player/jsmpeg/modules/source/ajax-progressive.js
  20. 0
      src/components/jsmpeg-player/jsmpeg/modules/source/ajax.js
  21. 0
      src/components/jsmpeg-player/jsmpeg/modules/source/fetch.js
  22. 0
      src/components/jsmpeg-player/jsmpeg/modules/source/index.js
  23. 8
      src/components/jsmpeg-player/jsmpeg/modules/source/websocket.js
  24. 4
      src/components/jsmpeg-player/jsmpeg/modules/video-element.js
  25. 5
      src/components/jsmpeg-player/jsmpeg/modules/wasm-module.js
  26. 0
      src/components/jsmpeg-player/jsmpeg/types/index.d.ts
  27. 6
      src/components/jsmpeg-player/jsmpeg/utils/index.js
  28. 70
      src/components/jsmpeg-player/styles/icon/iconfont.css
  29. BIN
      src/components/jsmpeg-player/styles/icon/iconfont.ttf
  30. BIN
      src/components/jsmpeg-player/styles/icon/iconfont.woff
  31. BIN
      src/components/jsmpeg-player/styles/icon/iconfont.woff2
  32. 19
      src/components/jsmpeg-player/styles/icon/index.css
  33. 183
      src/components/jsmpeg-player/styles/index.scss
  34. 100
      src/components/jsmpeg-player/styles/popover.scss
  35. 54
      src/views/storeManage/warehouse3D/module/video.vue

679
src/components/jsmpeg-player/jsmpeg-player.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-tabsel-tableBug
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>

0
src/components/jsmpeg/index.js → src/components/jsmpeg-player/jsmpeg/index.js

0
src/components/jsmpeg/modules/audio-output/index.js → src/components/jsmpeg-player/jsmpeg/modules/audio-output/index.js

0
src/components/jsmpeg/modules/audio-output/webaudio.js → src/components/jsmpeg-player/jsmpeg/modules/audio-output/webaudio.js

24
src/components/jsmpeg/modules/buffer.js → src/components/jsmpeg-player/jsmpeg/modules/buffer.js

@ -1,4 +1,3 @@
/* eslint-disable */
'use strict' 'use strict'
export default class BitBuffer { export default class BitBuffer {
/** @type {Uint8Array} */ /** @type {Uint8Array} */
@ -75,7 +74,8 @@ export default class BitBuffer {
// Calculate total byte length // Calculate total byte length
if (isArrayOfBuffers) { if (isArrayOfBuffers) {
// let totalLength = 0
// eslint-disable-next-line
let totalLength = 0
for (let i = 0; i < buffers.length; i++) { for (let i = 0; i < buffers.length; i++) {
totalLength += buffers[i].byteLength totalLength += buffers[i].byteLength
} }
@ -114,9 +114,12 @@ export default class BitBuffer {
findNextStartCode() { findNextStartCode() {
for (let i = (this.index + 7) >> 3; i < this.byteLength; i++) { for (let i = (this.index + 7) >> 3; i < this.byteLength; i++) {
if ( if (
this.bytes[i] === 0x00 &&
this.bytes[i + 1] === 0x00 &&
this.bytes[i + 2] === 0x01
// eslint-disable-next-line
this.bytes[i] == 0x00 &&
// eslint-disable-next-line
this.bytes[i + 1] == 0x00 &&
// eslint-disable-next-line
this.bytes[i + 2] == 0x01
) { ) {
this.index = (i + 4) << 3 this.index = (i + 4) << 3
return this.bytes[i + 3] return this.bytes[i + 3]
@ -128,12 +131,14 @@ export default class BitBuffer {
findStartCode(code) { findStartCode(code) {
let current = 0 let current = 0
// eslint-disable-next-line
while (true) { while (true) {
current = this.findNextStartCode() current = this.findNextStartCode()
if (current === code || current === -1) { if (current === code || current === -1) {
return current return current
} }
} }
// eslint-disable-next-line
return -1 return -1
} }
@ -141,9 +146,12 @@ export default class BitBuffer {
const i = (this.index + 7) >> 3 const i = (this.index + 7) >> 3
return ( return (
i >= this.byteLength || i >= this.byteLength ||
(this.bytes[i] === 0x00 &&
this.bytes[i + 1] === 0x00 &&
this.bytes[i + 2] === 0x01)
// eslint-disable-next-line
(this.bytes[i] == 0x00 &&
// eslint-disable-next-line
this.bytes[i + 1] == 0x00 &&
// eslint-disable-next-line
this.bytes[i + 2] == 0x01)
) )
} }

7
src/components/jsmpeg/modules/decoder/decoder.js → src/components/jsmpeg-player/jsmpeg/modules/decoder/decoder.js

@ -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
src/components/jsmpeg/modules/decoder/index.js → src/components/jsmpeg-player/jsmpeg/modules/decoder/index.js

0
src/components/jsmpeg/modules/decoder/mp2-wasm.js → src/components/jsmpeg-player/jsmpeg/modules/decoder/mp2-wasm.js

3
src/components/jsmpeg/modules/decoder/mp2.js → src/components/jsmpeg-player/jsmpeg/modules/decoder/mp2.js

@ -107,7 +107,8 @@ export default class MP2 extends BaseDecoder {
bitrateIndex += 14 bitrateIndex += 14
} }
const padding = this.bits.read(1) const padding = this.bits.read(1)
// const privat = this.bits.read(1)
// const privat =
this.bits.read(1)
const mode = this.bits.read(2) const mode = this.bits.read(2)
// Parse the mode_extension, set up the stereo bound // Parse the mode_extension, set up the stereo bound

0
src/components/jsmpeg/modules/decoder/mpeg1-wasm.js → src/components/jsmpeg-player/jsmpeg/modules/decoder/mpeg1-wasm.js

7
src/components/jsmpeg/modules/decoder/mpeg1.js → src/components/jsmpeg-player/jsmpeg/modules/decoder/mpeg1.js

@ -1,4 +1,3 @@
/* eslint-disable */
import { Fill, Now } from '../../utils' import { Fill, Now } from '../../utils'
import BitBuffer from '../buffer' import BitBuffer from '../buffer'
import BaseDecoder from './decoder' import BaseDecoder from './decoder'
@ -62,7 +61,8 @@ export default class MPEG1 extends BaseDecoder {
} }
if (this.bits.findStartCode(MPEG1.START.PICTURE) === -1) { if (this.bits.findStartCode(MPEG1.START.PICTURE) === -1) {
const bufferedBytes = this.bits.byteLength - (this.bits.index >> 3)
// const bufferedBytes =
this.bits.byteLength - (this.bits.index >> 3)
return false return false
} }
@ -107,7 +107,9 @@ export default class MPEG1 extends BaseDecoder {
if (this.destination) { if (this.destination) {
this.destination.resize(newWidth, newHeight) this.destination.resize(newWidth, newHeight)
// eslint-disable-next-line
this.resolution.width = w this.resolution.width = w
// eslint-disable-next-line
this.resolution.height = h this.resolution.height = h
this.options.onResolutionDecode?.(newWidth, newHeight) this.options.onResolutionDecode?.(newWidth, newHeight)
} }
@ -785,6 +787,7 @@ export default class MPEG1 extends BaseDecoder {
// Decode AC coefficients (+DC for non-intra) // Decode AC coefficients (+DC for non-intra)
let level = 0 let level = 0
// eslint-disable-next-line
while (true) { while (true) {
let run = 0 let run = 0
const coeff = this.readHuffman(MPEG1.DCT_COEFF) const coeff = this.readHuffman(MPEG1.DCT_COEFF)

0
src/components/jsmpeg/modules/demuxer/index.js → src/components/jsmpeg-player/jsmpeg/modules/demuxer/index.js

22
src/components/jsmpeg/modules/demuxer/ts.js → src/components/jsmpeg-player/jsmpeg/modules/demuxer/ts.js

@ -1,9 +1,8 @@
/* eslint-disable */
import BitBuffer from '../buffer' import BitBuffer from '../buffer'
import MP2 from '../decoder/mp2'
import MP2WASM from '../decoder/mp2-wasm'
import MPEG1 from '../decoder/mpeg1'
import MPEG1WASM from '../decoder/mpeg1-wasm'
// import MP2 from '../decoder/mp2'
// import MP2WASM from '../decoder/mp2-wasm'
// import MPEG1 from '../decoder/mpeg1'
// import MPEG1WASM from '../decoder/mpeg1-wasm'
export default class TS { export default class TS {
/** @type {BitBuffer} */ /** @type {BitBuffer} */
@ -41,6 +40,7 @@ export default class TS {
this.bits = new BitBuffer(buffer) this.bits = new BitBuffer(buffer)
} }
// eslint-disable-next-line
while (this.bits.has(188 << 3) && this.parsePacket()) {} while (this.bits.has(188 << 3) && this.parsePacket()) {}
const leftoverCount = this.bits.byteLength - (this.bits.index >> 3) const leftoverCount = this.bits.byteLength - (this.bits.index >> 3)
@ -58,13 +58,17 @@ export default class TS {
} }
const end = (this.bits.index >> 3) + 187 const end = (this.bits.index >> 3) + 187
const transportError = this.bits.read(1)
// const transportError =
this.bits.read(1)
const payloadStart = this.bits.read(1) const payloadStart = this.bits.read(1)
const transportPriority = this.bits.read(1)
// const transportPriority =
this.bits.read(1)
const pid = this.bits.read(13) const pid = this.bits.read(13)
const transportScrambling = this.bits.read(2)
// const transportScrambling =
this.bits.read(2)
const adaptationField = this.bits.read(2) const adaptationField = this.bits.read(2)
const continuityCounter = this.bits.read(4)
// const continuityCounter =
this.bits.read(4)
// If this is the start of a new payload; signal the end of the previous // If this is the start of a new payload; signal the end of the previous
// frame, if we didn't do so already. // frame, if we didn't do so already.

0
src/components/jsmpeg/modules/jsmpeg.js → src/components/jsmpeg-player/jsmpeg/modules/jsmpeg.js

30
src/components/jsmpeg/modules/player.js → src/components/jsmpeg-player/jsmpeg/modules/player.js

@ -1,14 +1,12 @@
/* eslint-disable */
import Renderer from './renderer' import Renderer from './renderer'
import { Base64ToArrayBuffer, saveToLocal, Now } from '../utils'
import { Base64ToArrayBuffer, download, Now } from '../utils'
import AudioOutput from './audio-output' import AudioOutput from './audio-output'
import Decoder from './decoder' import Decoder from './decoder'
import Demuxer from './demuxer' import Demuxer from './demuxer'
import Source from './source' import Source from './source'
import WASM from './wasm-module' import WASM from './wasm-module'
import AjaxSource from './source/ajax'
import AjaxProgressiveSource from './source/ajax-progressive'
// import AjaxSource from './source/ajax'
// import AjaxProgressiveSource from './source/ajax-progressive'
import WSSource from './source/websocket' import WSSource from './source/websocket'
import BitBuffer from './buffer' import BitBuffer from './buffer'
@ -191,6 +189,7 @@ export default class Player {
const options = this.options const options = this.options
this.url = url this.url = url
if (options.source) { if (options.source) {
// eslint-disable-next-line
this.source = new options.source(url, options) this.source = new options.source(url, options)
options.streaming = !!this.source.streaming options.streaming = !!this.source.streaming
} else if (url.match(/^wss?:\/\//)) { } else if (url.match(/^wss?:\/\//)) {
@ -335,7 +334,8 @@ export default class Player {
if ((Math.abs(angle) / 90) % 2 === 1) { if ((Math.abs(angle) / 90) % 2 === 1) {
// 如果是90整数倍,表示为垂直状态 // 如果是90整数倍,表示为垂直状态
const containerBound = this.contianer.getBoundingClientRect() const containerBound = this.contianer.getBoundingClientRect()
const canvasBound = canvas.getBoundingClientRect()
// const canvasBound =
canvas.getBoundingClientRect()
if (canvas.width > canvas.height) { if (canvas.width > canvas.height) {
// 宽>高,取容器高度作为canvas最大宽度 // 宽>高,取容器高度作为canvas最大宽度
@ -357,14 +357,14 @@ export default class Player {
* @author 刘灿民 * @author 刘灿民
* @param {string} name * @param {string} name
*/ */
snapshot(name = 'JSMPeg') {
snapshot(name = 'jsmpeg') {
if (this.canvas) { if (this.canvas) {
const mime = 'image/png' const mime = 'image/png'
const url = this.canvas.toDataURL(mime) const url = this.canvas.toDataURL(mime)
saveToLocal(
download(
url.replace(mime, 'image/octet-stream'), url.replace(mime, 'image/octet-stream'),
`${name}_截图_${new Date().toLocaleTimeString()}.png`,
`${name}_snapshot_${Date.now()}.png`,
mime mime
) )
} }
@ -390,7 +390,7 @@ export default class Player {
this.recorder.stream instanceof MediaStream this.recorder.stream instanceof MediaStream
) { ) {
this.recorder.mediaRecorder.stop() this.recorder.mediaRecorder.stop()
saveToLocal(
download(
this.recorder.chunks, this.recorder.chunks,
`${this.recorder.saveName}.webm`, `${this.recorder.saveName}.webm`,
'video/webm;codecs=vp9' 'video/webm;codecs=vp9'
@ -400,7 +400,7 @@ export default class Player {
this.recorder.mode === 'ws' && this.recorder.mode === 'ws' &&
this.recorder.chunks instanceof Array this.recorder.chunks instanceof Array
) { ) {
saveToLocal(
download(
this.recorder.chunks, this.recorder.chunks,
`${this.recorder.saveName}.ts`, `${this.recorder.saveName}.ts`,
'video/MP2T' 'video/MP2T'
@ -688,11 +688,9 @@ export default class Player {
this.options.onEnded(this) this.options.onEnded(this)
} }
} }
}
// If there's not enough data and the source is not completed, we have
// just stalled.
else if (notEnoughData && this.options.onStalled) {
} else if (notEnoughData && this.options.onStalled) {
// If there's not enough data and the source is not completed, we have
// just stalled.
this.options.onStalled(this) this.options.onStalled(this)
} }
} }

0
src/components/jsmpeg/modules/renderer/canvas2d.js → src/components/jsmpeg-player/jsmpeg/modules/renderer/canvas2d.js

0
src/components/jsmpeg/modules/renderer/index.js → src/components/jsmpeg-player/jsmpeg/modules/renderer/index.js

0
src/components/jsmpeg/modules/renderer/webgl.js → src/components/jsmpeg-player/jsmpeg/modules/renderer/webgl.js

0
src/components/jsmpeg/modules/source/ajax-progressive.js → src/components/jsmpeg-player/jsmpeg/modules/source/ajax-progressive.js

0
src/components/jsmpeg/modules/source/ajax.js → src/components/jsmpeg-player/jsmpeg/modules/source/ajax.js

0
src/components/jsmpeg/modules/source/fetch.js → src/components/jsmpeg-player/jsmpeg/modules/source/fetch.js

0
src/components/jsmpeg/modules/source/index.js → src/components/jsmpeg-player/jsmpeg/modules/source/index.js

8
src/components/jsmpeg/modules/source/websocket.js → src/components/jsmpeg-player/jsmpeg/modules/source/websocket.js

@ -1,8 +1,6 @@
/* eslint-disable */
'use strict' 'use strict'
import TS from '../demuxer/ts'
// import TS from '../demuxer/ts'
export default class WSSource { export default class WSSource {
timer = { timer = {
@ -81,7 +79,7 @@ export default class WSSource {
clearTimeout(this.reconnectTimeoutId) clearTimeout(this.reconnectTimeoutId)
this.reconnectTimeoutId = 0 this.reconnectTimeoutId = 0
this.shouldAttemptReconnect = false this.shouldAttemptReconnect = false
this.socket && this.socket.close()
this.socket?.close()
if (this.socket) { if (this.socket) {
this.socket.onmessage = null this.socket.onmessage = null
this.socket.onopen = null this.socket.onopen = null
@ -157,7 +155,7 @@ export default class WSSource {
} }
onError(err) { onError(err) {
// console.error(err)
console.error(err)
} }
onClose() { onClose() {

4
src/components/jsmpeg/modules/video-element.js → src/components/jsmpeg-player/jsmpeg/modules/video-element.js

@ -1,4 +1,3 @@
/* eslint-disable */
import Player from './player' import Player from './player'
export default class VideoElement { export default class VideoElement {
@ -6,7 +5,8 @@ export default class VideoElement {
const url = element.dataset.url const url = element.dataset.url
if (!url) { if (!url) {
throw 'VideoElement has no `data-url` attribute'
const error = { message: 'VideoElement has no `data-url` attribute' }
throw error
} }
// Setup the div container, canvas and play button // Setup the div container, canvas and play button

5
src/components/jsmpeg/modules/wasm-module.js → src/components/jsmpeg-player/jsmpeg/modules/wasm-module.js

@ -1,5 +1,3 @@
/* eslint-disable */
import Source from './source' import Source from './source'
export default class WASM { export default class WASM {
@ -103,6 +101,7 @@ export default class WASM {
function readVarUint() { function readVarUint() {
let ret = 0 let ret = 0
let mul = 1 let mul = 1
// eslint-disable-next-line
while (1) { while (1) {
const byte = bytes[next++] const byte = bytes[next++]
ret += (byte & 0x7f) * mul ret += (byte & 0x7f) * mul
@ -134,7 +133,7 @@ export default class WASM {
// Make sure we have a dylink section // Make sure we have a dylink section
next = 9 next = 9
const sectionSize = readVarUint()
readVarUint()
if (!matchNextBytes([6, 'd', 'y', 'l', 'i', 'n', 'k'])) { if (!matchNextBytes([6, 'd', 'y', 'l', 'i', 'n', 'k'])) {
console.warn('JSMpeg: No dylink section found in WASM') console.warn('JSMpeg: No dylink section found in WASM')
return null return null

0
src/components/jsmpeg/types/index.d.ts → src/components/jsmpeg-player/jsmpeg/types/index.d.ts

6
src/components/jsmpeg/utils/index.js → src/components/jsmpeg-player/jsmpeg/utils/index.js

@ -33,11 +33,7 @@ export function Base64ToArrayBuffer(base64) {
* @param {string} param.mimeType 文件mime类型 * @param {string} param.mimeType 文件mime类型
* @returns * @returns
*/ */
export function saveToLocal(
blob,
name = 'JSMpeg_' + Date.now(),
mimeType = ''
) {
export function download(blob, name = 'JSMpeg_' + Date.now(), mimeType = '') {
if (!blob) return if (!blob) return
const a = document.createElement('a') const a = document.createElement('a')

70
src/components/jsmpeg-player/styles/icon/iconfont.css

@ -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';
}

BIN
src/components/jsmpeg-player/styles/icon/iconfont.ttf

BIN
src/components/jsmpeg-player/styles/icon/iconfont.woff

BIN
src/components/jsmpeg-player/styles/icon/iconfont.woff2

19
src/components/jsmpeg-player/styles/icon/index.css

@ -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;
}

183
src/components/jsmpeg-player/styles/index.scss

@ -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;
}
}

100
src/components/jsmpeg-player/styles/popover.scss

@ -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;
}
}
}
}
}
}

54
src/views/storeManage/warehouse3D/module/video.vue

@ -1,23 +1,26 @@
<template> <template>
<el-dialog :close-on-click-modal="false" :visible.sync="openVideoVisible" :before-close="handleClose" :title="videoTitle" width="1068px" @opened="opened">
<el-dialog :close-on-click-modal="false" :visible.sync="openVideoVisible" :before-close="handleClose" :title="videoTitle" width="1068px">
<span class="dialog-right-top" /> <span class="dialog-right-top" />
<span class="dialog-left-bottom" /> <span class="dialog-left-bottom" />
<div class="setting-dialog"> <div class="setting-dialog">
<!-- :src="videoSrc" controls--> <!-- :src="videoSrc" controls-->
<div ref="canvas-wrap" style="height:768px">
<jsmpeg-player :url="wsUrl" :with-toolbar="false" />
<!-- <div ref="canvas-wrap" style="height:768px">
<template v-if="noSignal"> <template v-if="noSignal">
<div> 无信号 </div> <div> 无信号 </div>
</template> </template>
</div>
</div> -->
</div> </div>
</el-dialog> </el-dialog>
</template> </template>
<script> <script>
import JSMpeg from '@/components/jsmpeg'
// import JSMpeg from '@/components/jsmpeg'
import JsmpegPlayer from '@/components/jsmpeg-player/jsmpeg-player.vue'
import axios from 'axios' import axios from 'axios'
import qs from 'qs' import qs from 'qs'
export default { export default {
components: { JsmpegPlayer },
props: { props: {
dialogOpen: { dialogOpen: {
type: Boolean, type: Boolean,
@ -29,7 +32,8 @@ export default {
videoTitle: '', videoTitle: '',
camConfig: null, camConfig: null,
player: null, player: null,
noSignal: true
noSignal: true,
wsUrl: ''
} }
}, },
computed: { computed: {
@ -45,11 +49,6 @@ export default {
} }
} }
}, },
beforeDestroy() {
this.player?.stop()
this.player?.destroy()
this.player = null
},
methods: { methods: {
handleClose(done) { handleClose(done) {
this.openVideoVisible = false this.openVideoVisible = false
@ -66,15 +65,10 @@ export default {
} }
} }
).then(res => { ).then(res => {
this.player?.stop()
this.player?.destroy()
this.player = null
this.wsUrl = ''
done() done()
}) })
}, },
opened() {
// this.play()
},
play() { play() {
const data = { rtspUrl: this.rtspUrl, size: '1024*768', port: process.env.VUE_APP_CAMERA_API.split(':')[1], videoRoute: this.camConfig.deviceInfo.videoRoute } const data = { rtspUrl: this.rtspUrl, size: '1024*768', port: process.env.VUE_APP_CAMERA_API.split(':')[1], videoRoute: this.camConfig.deviceInfo.videoRoute }
const Uri = 'http://' + process.env.VUE_APP_CAMERA_API + '/cameras/' const Uri = 'http://' + process.env.VUE_APP_CAMERA_API + '/cameras/'
@ -87,21 +81,21 @@ export default {
).then(res => { ).then(res => {
// res.data.port // res.data.port
const ip = process.env.VUE_APP_CAMERA_API.split(':')[0] const ip = process.env.VUE_APP_CAMERA_API.split(':')[0]
const url = 'ws://' + ip + ':' + res.data.port
this.wsUrl = 'ws://' + ip + ':' + res.data.port
// var canvas = document.getElementById('canvas') // var canvas = document.getElementById('canvas')
const opt = {
contianer: this.$refs['canvas-wrap'],
poster: '0.jpg',
onSourceEstablished: (source) => {
console.log('onSourceEstablished')
// this.flags.noSignal = false
this.noSignal = false
// clearTimeout(this.timers.noSignal)
// this.timers.noSignal = null
this.$emit('source-established', source)
}
}
this.player = new JSMpeg.Player(url, opt)
// const opt = {
// contianer: this.$refs['canvas-wrap'],
// poster: '0.jpg',
// onSourceEstablished: (source) => {
// console.log('onSourceEstablished')
// // this.flags.noSignal = false
// this.noSignal = false
// // clearTimeout(this.timers.noSignal)
// // this.timers.noSignal = null
// this.$emit('source-established', source)
// }
// }
// this.player = new JSMpeg.Player(this.wsUrl, opt)
}) })
} }
} }

Loading…
Cancel
Save