前言:
之前分享的有 rtmp直播流 ,flv直播流的一些方法,这里分享下,播放 websocket的直流的方法,使用的方法是JSMpeg,JSMpeg是JS写的视频、音频解码器,能使用WebGL&Canvas2D渲染以及WebAudio声音输出。
目录:
封装成组件,做视频的墙的效果,但是一页放9个以后,第一个视频会崩溃,查了资料以后,目前暂定支持8个,可能跟电脑配置也有很大的关系,1个使用,4个使用没发现问题
相关资料:
1、官网入口
2、github
3、官方例子
4、gitee案例
实现效果:
暂无图片
遇到问题:
封装成组件,做视频的墙的效果,但是一页放9个以后,第一个视频会崩溃,查了资料以后,目前暂定支持8个,可能跟电脑配置也有很大的关系,1个使用,4个使用没发现问题
实现步骤:
1、引入配置文件:jsmpeg.min.js,源码看下面
2、main.js中调用该文件
import '/src/util/jsmpeg.min.js'
3、页面使用:
template:
<div class="myVideo">
<canvas class="video-canvas" ref="video-canvas" style="width:100%;"></canvas>
</div>
js中:
this.path = 'ws:...' //视频流地址
var canvas = this.$refs['video-canvas']
this.player = new JSMpeg.Player(this.path, { canvas: canvas })
js中 方法二:
var player = new JSMpeg.Player('test.ts', {
canvas: document.getElementById('video'),
decodeFirstFrame: true,
disableWebAssembly: false,
throttled: false, //这里设置为false,不然不触发onSourceCompleted事件
chunkSize: 4 * 1024 * 1024,
disableGl: false,
audio: true,
autoplay:true,
loop:false,
onSourceCompleted:()=>{
}
});
player.play();
api配置:
/** 是否循环播放视频(仅静态文件)。默认true */
autoplay: true,
/** 是否解码音频。默认true */
audio: true,
/** 是否解码视频。默认true */
video: true,
/** 一个图像的URL,用来在视频播放之前作为海报显示。 */
poster: null,
/** 是否禁用后台播放,当TAB处于非活动状态时是否暂停播放。注意,浏览器通常会在非活动标签中限制JS。默认true */
pauseWhenHidden: true,
/** 是否禁用WebGL,始终使用Canvas2D渲染器。默认.false */
disableGl: false,
/** 是否禁用WebAssembly并始终使用JavaScript解码器。默认false */
disableWebAssembly: false,
/** WebGL上下文是否创建-必要的“截图”通过。默认false */
preserveDrawingBuffer: true,
/** 是否以块的形式加载数据(仅静态文件)。当启用时,回放可以在完整加载源之前开始 */
progressive: true,
/** 当不需要回放时是否推迟加载块。默认=progressive */
throttled: true,
/** 使用时,以字节为单位加载的块大小。默认(1 mb)1024*1024 */
chunkSize: 1024 * 1024,
/** 是否解码并显示视频的第一帧。设置画布大小和使用框架作为“海报”图像很有用。这在使用或流源时没有影响。默认true */
decodeFirstFrame: false,
/** 流媒体时,以秒为单位的最大排队音频长度。 */
maxAudioLag: 0.25,
/** 流媒体时,视频解码缓冲区的字节大小。默认的512 * 1024 (512 kb)。对于非常高的比特率,您可能需要增加此值。 */
videoBufferSize: 1024 * 1024,
/** 流媒体时,音频解码缓冲区的字节大小。默认的128 * 1024 (128 kb)。对于非常高的比特率,您可能需要增加此值。 */
audioBufferSize: 256 * 1024
4、jsmpeg.min.js源码:
window.JSMpeg = { Player: null, VideoElement: null, BitBuffer: null, Source: {}, Demuxer: {}, Decoder: {}, Renderer: {}, AudioOutput: {}, Now: function () { return window.performance ? window.performance.now() / 1e3 : Date.now() / 1e3 }, CreateVideoElements: function () { var elements = document.querySelectorAll(".jsmpeg"); for (var i = 0; i < elements.length; i++) { new JSMpeg.VideoElement(elements[i]) } }, Fill: function (array, value) { if (array.fill) { array.fill(value) } else { for (var i = 0; i < array.length; i++) { array[i] = value } } }, Base64ToArrayBuffer: function (base64) { var binary = window.atob(base64); var length = binary.length; var bytes = new Uint8Array(length); for (var i = 0; i < length; i++) { bytes[i] = binary.charCodeAt(i) } return bytes.buffer }, WASM_BINARY_INLINED: null }; if (document.readyState === "complete") { JSMpeg.CreateVideoElements() } else { document.addEventListener("DOMContentLoaded", JSMpeg.CreateVideoElements) } JSMpeg.VideoElement = function () { "use strict"; var VideoElement = function (element) { var url = element.dataset.url; if (!url) { throw "VideoElement has no `data-url` attribute" } var addStyles = function (element, styles) { for (var name in styles) { element.style[name] = styles[name] } }; this.container = element; addStyles(this.container, { display: "inline-block", position: "relative", minWidth: "80px", minHeight: "80px" }); this.canvas = document.createElement("canvas"); this.canvas.width = 960; this.canvas.height = 540; addStyles(this.canvas, { display: "block", width: "100%" }); this.container.appendChild(this.canvas); this.playButton = document.createElement("div"); this.playButton.innerHTML = VideoElement.PLAY_BUTTON; addStyles(this.playButton, { zIndex: 2, position: "absolute", top: "0", bottom: "0", left: "0", right: "0", maxWidth: "75px", maxHeight: "75px", margin: "auto", opacity: "0.7", cursor: "pointer" }); this.container.appendChild(this.playButton); var options = { canvas: this.canvas }; for (var option in element.dataset) { try { options[option] = JSON.parse(element.dataset[option]) } catch (err) { options[option] = element.dataset[option] } } this.player = new JSMpeg.Player(url, options); element.playerInstance = this.player; if (options.poster && !options.autoplay && !this.player.options.streaming) { options.decodeFirstFrame = false; this.poster = new Image; this.poster.src = options.poster; this.poster.addEventListener("load", this.posterLoaded); addStyles(this.poster, { display: "block", zIndex: 1, position: "absolute", top: 0, left: 0, bottom: 0, right: 0 }); this.container.appendChild(this.poster) } if (!this.player.options.streaming) { this.container.addEventListener("click", this.onClick.bind(this)) } if (options.autoplay || this.player.options.streaming) { this.playButton.style.display = "none" } if (this.player.audioOut && !this.player.audioOut.unlocked) { var unlockAudioElement = this.container; if (options.autoplay || this.player.options.streaming) { this.unmuteButton = document.createElement("div"); this.unmuteButton.innerHTML = VideoElement.UNMUTE_BUTTON; addStyles(this.unmuteButton, { zIndex: 2, position: "absolute", bottom: " 0.5208333333333334vw", right: "20px", width: "75px", height: "75px", margin: "auto", opacity: "0.7", cursor: "pointer" }); this.container.appendChild(this.unmuteButton); unlockAudioElement = this.unmuteButton } this.unlockAudioBound = this.onUnlockAudio.bind(this, unlockAudioElement); unlockAudioElement.addEventListener("touchstart", this.unlockAudioBound, false); unlockAudioElement.addEventListener("click", this.unlockAudioBound, true) } }; VideoElement.prototype.onUnlockAudio = function (element, ev) { if (this.unmuteButton) { ev.preventDefault(); ev.stopPropagation() } this.player.audioOut.unlock(function () { if (this.unmuteButton) { this.unmuteButton.style.display = "none" } element.removeEventListener("touchstart", this.unlockAudioBound); element.removeEventListener("click", this.unlockAudioBound) }.bind(this)) }; VideoElement.prototype.onClick = function (ev) { if (this.player.isPlaying) { this.player.pause(); this.playButton.style.display = "block" } else { this.player.play(); this.playButton.style.display = "none"; if (this.poster) { this.poster.style.display = "none" } } }; VideoElement.PLAY_BUTTON = '<svg style="max-width: 75px; max-height: 75px;" ' + 'viewBox="0 0 200 200" alt="Play video">' + '<circle cx="100" cy="100" r="90" fill="none" ' + 'stroke-width="15" stroke="#fff"/>' + '<polygon points="70, 55 70, 145 145, 100" fill="#fff"/>' + "</svg>"; VideoElement.UNMUTE_BUTTON = '<svg style="max-width: 75px; max-height: 75px;" viewBox="0 0 75 75">' + '<polygon class="audio-speaker" stroke="none" fill="#fff" ' + 'points="39,13 22,28 6,28 6,47 21,47 39,62 39,13"/>' + '<g stroke="#fff" stroke-width="5">' + '<path d="M 49,50 69,26"/>' + '<path d="M 69,50 49,26"/>' + "</g>" + "</svg>"; return VideoElement }(); JSMpeg.Player = function () { "use strict"; var Player = function (url, options) { this.options = options || {}; if (options.source) { this.source = new options.source(url, options); options.streaming = !!this.source.streaming } else if (url.match(/^wss?:\/\//)) { this.source = new JSMpeg.Source.WebSocket(url, options); options.streaming = true } else if (options.progressive !== false) { this.source = new JSMpeg.Source.AjaxProgressive(url, options); options.streaming = false } else { this.source = new JSMpeg.Source.Ajax(url, options); options.streaming = false } this.maxAudioLag = options.maxAudioLag || .25; this.loop = options.loop !== false; this.autoplay = !!options.autoplay || options.streaming; this.demuxer = new JSMpeg.Demuxer.TS(options); this.source.connect(this.demuxer); if (!options.disableWebAssembly && JSMpeg.WASMModule.IsSupported()) { this.wasmModule = JSMpeg.WASMModule.GetModule(); options.wasmModule = this.wasmModule } if (options.video !== false) { this.video = options.wasmModule ? new JSMpeg.Decoder.MPEG1VideoWASM(options) : new JSMpeg.Decoder.MPEG1Video(options); this.renderer = !options.disableGl && JSMpeg.Renderer.WebGL.IsSupported() ? new JSMpeg.Renderer.WebGL(options) : new JSMpeg.Renderer.Canvas2D(options); this.demuxer.connect(JSMpeg.Demuxer.TS.STREAM.VIDEO_1, this.video); this.video.connect(this.renderer) } if (options.audio !== false && JSMpeg.AudioOutput.WebAudio.IsSupported()) { this.audio = options.wasmModule ? new JSMpeg.Decoder.MP2AudioWASM(options) : new JSMpeg.Decoder.MP2Audio(options); this.audioOut = new JSMpeg.AudioOutput.WebAudio(options); this.demuxer.connect(JSMpeg.Demuxer.TS.STREAM.AUDIO_1, this.audio); this.audio.connect(this.audioOut) } Object.defineProperty(this, "currentTime", { get: this.getCurrentTime, set: this.setCurrentTime }); Object.defineProperty(this, "volume", { get: this.getVolume, set: this.setVolume }); this.paused = true; this.unpauseOnShow = false; if (options.pauseWhenHidden !== false) { document.addEventListener("visibilitychange", this.showHide.bind(this)) } if (this.wasmModule) { if (this.wasmModule.ready) { this.startLoading() } else if (JSMpeg.WASM_BINARY_INLINED) { var wasm = JSMpeg.Base64ToArrayBuffer(JSMpeg.WASM_BINARY_INLINED); this.wasmModule.loadFromBuffer(wasm, this.startLoading.bind(this)) } else { this.wasmModule.loadFromFile("jsmpeg.wasm", this.startLoading.bind(this)) } } else { this.startLoading() } }; Player.prototype.startLoading = function () { this.source.start(); if (this.autoplay) { this.play() } }; Player.prototype.showHide = function (ev) { if (document.visibilityState === "hidden") { this.unpauseOnShow = this.wantsToPlay; this.pause() } else if (this.unpauseOnShow) { this.play() } }; Player.prototype.play = function (ev) { if (this.animationId) { return } this.animationId = requestAnimationFrame(this.update.bind(this)); this.wantsToPlay = true; this.paused = false }; Player.prototype.pause = function (ev) { if (this.paused) { return } cancelAnimationFrame(this.animationId); this.animationId = null; this.wantsToPlay = false; this.isPlaying = false; this.paused = true; if (this.audio && this.audio.canPlay) { this.audioOut.stop(); this.seek(this.currentTime) } if (this.options.onPause) { this.options.onPause(this) } }; Player.prototype.getVolume = function () { return this.audioOut ? this.audioOut.volume : 0 }; Player.prototype.setVolume = function (volume) { if (this.audioOut) { this.audioOut.volume = volume } }; Player.prototype.stop = function (ev) { this.pause(); this.seek(0); if (this.video && this.options.decodeFirstFrame !== false) { this.video.decode() } }; Player.prototype.destroy = function () { this.pause(); this.source.destroy(); this.video && this.video.destroy(); this.renderer && this.renderer.destroy(); this.audio && this.audio.destroy(); this.audioOut && this.audioOut.destroy() }; Player.prototype.seek = function (time) { var startOffset = this.audio && this.audio.canPlay ? this.audio.startTime : this.video.startTime; if (this.video) { this.video.seek(time + startOffset) } if (this.audio) { this.audio.seek(time + startOffset) } this.startTime = JSMpeg.Now() - time }; Player.prototype.getCurrentTime = function () { return this.audio && this.audio.canPlay ? this.audio.currentTime - this.audio.startTime : this.video.currentTime - this.video.startTime }; Player.prototype.setCurrentTime = function (time) { this.seek(time) }; Player.prototype.update = function () { this.animationId = requestAnimationFrame(this.update.bind(this)); if (!this.source.established) { if (this.renderer) { this.renderer.renderProgress(this.source.progress) } return } if (!this.isPlaying) { this.isPlaying = true; this.startTime = JSMpeg.Now() - this.currentTime; if (this.options.onPlay) { this.options.onPlay(this) } } if (this.options.streaming) { this.updateForStreaming() } else { this.updateForStaticFile() } }; Player.prototype.updateForStreaming = function () { if (this.video) { this.video.decode() } if (this.audio) { var decoded = false; do { if (this.audioOut.enqueuedTime > this.maxAudioLag) { this.audioOut.resetEnqueuedTime(); this.audioOut.enabled = false } decoded = this.audio.decode() } while (decoded); this.audioOut.enabled = true } }; Player.prototype.nextFrame = function () { if (this.source.established && this.video) { return this.video.decode() } return false }; Player.prototype.updateForStaticFile = function () { var notEnoughData = false, headroom = 0; if (this.audio && this.audio.canPlay) { while (!notEnoughData && this.audio.decodedTime - this.audio.currentTime < .25) { notEnoughData = !this.audio.decode() } if (this.video && this.video.currentTime < this.audio.currentTime) { notEnoughData = !this.video.decode() } headroom = this.demuxer.currentTime - this.audio.currentTime } else if (this.video) { var targetTime = JSMpeg.Now() - this.startTime + this.video.startTime, lateTime = targetTime - this.video.currentTime, frameTime = 1 / this.video.frameRate; if (this.video && lateTime > 0) { if (lateTime > frameTime * 2) { this.startTime += lateTime } notEnoughData = !this.video.decode() } headroom = this.demuxer.currentTime - targetTime } this.source.resume(headroom); if (notEnoughData && this.source.completed) { if (this.loop) { this.seek(0) } else { this.pause(); if (this.options.onEnded) { this.options.onEnded(this) } } } else if (notEnoughData && this.options.onStalled) { this.options.onStalled(this) } }; return Player }(); JSMpeg.BitBuffer = function () { "use strict"; var BitBuffer = function (bufferOrLength, mode) { if (typeof bufferOrLength === "object") { this.bytes = bufferOrLength instanceof Uint8Array ? bufferOrLength : new Uint8Array(bufferOrLength); this.byteLength = this.bytes.length } else { this.bytes = new Uint8Array(bufferOrLength || 1024 * 1024); this.byteLength = 0 } this.mode = mode || BitBuffer.MODE.EXPAND; this.index = 0 }; BitBuffer.prototype.resize = function (size) { var newBytes = new Uint8Array(size); if (this.byteLength !== 0) { this.byteLength = Math.min(this.byteLength, size); newBytes.set(this.bytes, 0, this.byteLength) } this.bytes = newBytes; this.index = Math.min(this.index, this.byteLength << 3) }; BitBuffer.prototype.evict = function (sizeNeeded) { var bytePos = this.index >> 3, available = this.bytes.length - this.byteLength; if (this.index === this.byteLength << 3 || sizeNeeded > available + bytePos) { this.byteLength = 0; this.index = 0; return } else if (bytePos === 0) { return } if (this.bytes.copyWithin) { this.bytes.copyWithin(0, bytePos, this.byteLength) } else { this.bytes.set(this.bytes.subarray(bytePos, this.byteLength)) } this.byteLength = this.byteLength - bytePos; this.index -= bytePos << 3; return }; BitBuffer.prototype.write = function (buffers) { var isArrayOfBuffers = typeof buffers[0] === "object", totalLength = 0, available = this.bytes.length - this.byteLength; if (isArrayOfBuffers) { var totalLength = 0; for (var i = 0; i < buffers.length; i++) { totalLength += buffers[i].byteLength } } else { totalLength = buffers.byteLength } if (totalLength > available) { if (this.mode === BitBuffer.MODE.EXPAND) { var newSize = Math.max(this.bytes.length * 2, totalLength - available); this.resize(newSize) } else { this.evict(totalLength) } } if (isArrayOfBuffers) { for (var i = 0; i < buffers.length; i++) { this.appendSingleBuffer(buffers[i]) } } else { this.appendSingleBuffer(buffers) } return totalLength }; BitBuffer.prototype.appendSingleBuffer = function (buffer) { buffer = buffer instanceof Uint8Array ? buffer : new Uint8Array(buffer); this.bytes.set(buffer, this.byteLength); this.byteLength += buffer.length }; BitBuffer.prototype.findNextStartCode = function () { for (var i = this.index + 7 >> 3; i < this.byteLength; i++) { if (this.bytes[i] == 0 && this.bytes[i + 1] == 0 && this.bytes[i + 2] == 1) { this.index = i + 4 << 3; return this.bytes[i + 3] } } this.index = this.byteLength << 3; return -1 }; BitBuffer.prototype.findStartCode = function (code) { var current = 0; while (true) { current = this.findNextStartCode(); if (current === code || current === -1) { return current } } return -1 }; BitBuffer.prototype.nextBytesAreStartCode = function () { var i = this.index + 7 >> 3; return i >= this.byteLength || this.bytes[i] == 0 && this.bytes[i + 1] == 0 && this.bytes[i + 2] == 1 }; BitBuffer.prototype.peek = function (count) { var offset = this.index; var value = 0; while (count) { var currentByte = this.bytes[offset >> 3], remaining = 8 - (offset & 7), read = remaining < count ? remaining : count, shift = remaining - read, mask = 255 >> 8 - read; value = value << read | (currentByte & mask << shift) >> shift; offset += read; count -= read } return value }; BitBuffer.prototype.read = function (count) { var value = this.peek(count); this.index += count; return value }; BitBuffer.prototype.skip = function (count) { return this.index += count }; BitBuffer.prototype.rewind = function (count) { this.index = Math.max(this.index - count, 0) }; BitBuffer.prototype.has = function (count) { return (this.byteLength << 3) - this.index >= count }; BitBuffer.MODE = { EVICT: 1, EXPAND: 2 }; return BitBuffer }(); JSMpeg.Source.Ajax = function () { "use strict"; var AjaxSource = function (url, options) { this.url = url; this.destination = null; this.request = null; this.streaming = false; this.completed = false; this.established = false; this.progress = 0; this.onEstablishedCallback = options.onSourceEstablished; this.onCompletedCallback = options.onSourceCompleted }; AjaxSource.prototype.connect = function (destination) { this.destination = destination }; AjaxSource.prototype.start = function () { this.request = new XMLHttpRequest; this.request.onreadystatechange = function () { if (this.request.readyState === this.request.DONE && this.request.status === 200) { this.onLoad(this.request.response) } }.bind(this); this.request.onprogress = this.onProgress.bind(this); this.request.open("GET", this.url); this.request.responseType = "arraybuffer"; this.request.send() }; AjaxSource.prototype.resume = function (secondsHeadroom) { }; AjaxSource.prototype.destroy = function () { this.request.abort() }; AjaxSource.prototype.onProgress = function (ev) { this.progress = ev.loaded / ev.total }; AjaxSource.prototype.onLoad = function (data) { this.established = true; this.completed = true; this.progress = 1; if (this.onEstablishedCallback) { this.onEstablishedCallback(this) } if (this.onCompletedCallback) { this.onCompletedCallback(this) } if (this.destination) { this.destination.write(data) } }; return AjaxSource }(); JSMpeg.Source.Fetch = function () { "use strict"; var FetchSource = function (url, options) { this.url = url; this.destination = null; this.request = null; this.streaming = true; this.completed = false; this.established = false; this.progress = 0; this.aborted = false; this.onEstablishedCallback = options.onSourceEstablished; this.onCompletedCallback = options.onSourceCompleted }; FetchSource.prototype.connect = function (destination) { this.destination = destination }; FetchSource.prototype.start = function () { var params = { method: "GET", headers: new Headers, cache: "default" }; self.fetch(this.url, params).then(function (res) { if (res.ok && (res.status >= 200 && res.status <= 299)) { this.progress = 1; this.established = true; return this.pump(res.body.getReader()) } else { } }.bind(this)).catch(function (err) { throw err }) }; FetchSource.prototype.pump = function (reader) { return reader.read().then(function (result) { if (result.done) { this.completed = true } else { if (this.aborted) { return reader.cancel() } if (this.destination) { this.destination.write(result.value.buffer) } return this.pump(reader) } }.bind(this)).catch(function (err) { throw err }) }; FetchSource.prototype.resume = function (secondsHeadroom) { }; FetchSource.prototype.abort = function () { this.aborted = true }; return FetchSource }(); JSMpeg.Source.AjaxProgressive = function () { "use strict"; var AjaxProgressiveSource = function (url, options) { this.url = url; this.destination = null; this.request = null; this.streaming = false; this.completed = false; this.established = false; this.progress = 0; this.fileSize = 0; this.loadedSize = 0; this.chunkSize = options.chunkSize || 1024 * 1024; this.isLoading = false; this.loadStartTime = 0; this.throttled = options.throttled !== false; this.aborted = false; this.onEstablishedCallback = options.onSourceEstablished; this.onCompletedCallback = options.onSourceCompleted }; AjaxProgressiveSource.prototype.connect = function (destination) { this.destination = destination }; AjaxProgressiveSource.prototype.start = function () { this.request = new XMLHttpRequest; this.request.onreadystatechange = function () { if (this.request.readyState === this.request.DONE) { this.fileSize = parseInt(this.request.getResponseHeader("Content-Length")); this.loadNextChunk() } }.bind(this); this.request.onprogress = this.onProgress.bind(this); this.request.open("HEAD", this.url); this.request.send() }; AjaxProgressiveSource.prototype.resume = function (secondsHeadroom) { if (this.isLoading || !this.throttled) { return } var worstCaseLoadingTime = this.loadTime * 8 + 2; if (worstCaseLoadingTime > secondsHeadroom) { this.loadNextChunk() } }; AjaxProgressiveSource.prototype.destroy = function () { this.request.abort(); this.aborted = true }; AjaxProgressiveSource.prototype.loadNextChunk = function () { var start = this.loadedSize, end = Math.min(this.loadedSize + this.chunkSize - 1, this.fileSize - 1); if (start >= this.fileSize || this.aborted) { this.completed = true; if (this.onCompletedCallback) { this.onCompletedCallback(this) } return } this.isLoading = true; this.loadStartTime = JSMpeg.Now(); this.request = new XMLHttpRequest; this.request.onreadystatechange = function () { if (this.request.readyState === this.request.DONE && this.request.status >= 200 && this.request.status < 300) { this.onChunkLoad(this.request.response) } else if (this.request.readyState === this.request.DONE) { if (this.loadFails++ < 3) { this.loadNextChunk() } } }.bind(this); if (start === 0) { this.request.onprogress = this.onProgress.bind(this) } this.request.open("GET", this.url + "?" + start + "-" + end); this.request.setRequestHeader("Range", "bytes=" + start + "-" + end); this.request.responseType = "arraybuffer"; this.request.send() }; AjaxProgressiveSource.prototype.onProgress = function (ev) { this.progress = ev.loaded / ev.total }; AjaxProgressiveSource.prototype.onChunkLoad = function (data) { var isFirstChunk = !this.established; this.established = true; this.progress = 1; this.loadedSize += data.byteLength; this.loadFails = 0; this.isLoading = false; if (isFirstChunk && this.onEstablishedCallback) { this.onEstablishedCallback(this) } if (this.destination) { this.destination.write(data) } this.loadTime = JSMpeg.Now() - this.loadStartTime; if (!this.throttled) { this.loadNextChunk() } }; return AjaxProgressiveSource }(); JSMpeg.Source.WebSocket = function () { "use strict"; var WSSource = function (url, options) { this.url = url; this.options = options; this.socket = null; this.streaming = true; this.callbacks = { connect: [], data: [] }; this.destination = null; this.reconnectInterval = options.reconnectInterval !== undefined ? options.reconnectInterval : 5; this.shouldAttemptReconnect = !!this.reconnectInterval; this.completed = false; this.established = false; this.progress = 0; this.reconnectTimeoutId = 0; this.onEstablishedCallback = options.onSourceEstablished; this.onCompletedCallback = options.onSourceCompleted }; WSSource.prototype.connect = function (destination) { this.destination = destination }; WSSource.prototype.destroy = function () { clearTimeout(this.reconnectTimeoutId); this.shouldAttemptReconnect = false; this.socket.close() }; WSSource.prototype.start = function () { this.shouldAttemptReconnect = !!this.reconnectInterval; this.progress = 0; this.established = false; this.socket = new WebSocket(this.url, this.options.protocols || null); this.socket.binaryType = "arraybuffer"; this.socket.onmessage = this.onMessage.bind(this); this.socket.onopen = this.onOpen.bind(this); this.socket.onerror = this.onClose.bind(this); this.socket.onclose = this.onClose.bind(this) }; WSSource.prototype.resume = function (secondsHeadroom) { }; WSSource.prototype.onOpen = function () { this.progress = 1 }; WSSource.prototype.onClose = function () { if (this.shouldAttemptReconnect) { clearTimeout(this.reconnectTimeoutId); this.reconnectTimeoutId = setTimeout(function () { this.start() }.bind(this), this.reconnectInterval * 1e3) } }; WSSource.prototype.onMessage = function (ev) { var isFirstChunk = !this.established; this.established = true; if (isFirstChunk && this.onEstablishedCallback) { this.onEstablishedCallback(this) } if (this.destination) { this.destination.write(ev.data) } }; return WSSource }(); JSMpeg.Demuxer.TS = function () { "use strict"; var TS = function (options) { this.bits = null; this.leftoverBytes = null; this.guessVideoFrameEnd = true; this.pidsToStreamIds = {}; this.pesPacketInfo = {}; this.startTime = 0; this.currentTime = 0 }; TS.prototype.connect = function (streamId, destination) { this.pesPacketInfo[streamId] = { destination: destination, currentLength: 0, totalLength: 0, pts: 0, buffers: [] } }; TS.prototype.write = function (buffer) { if (this.leftoverBytes) { var totalLength = buffer.byteLength + this.leftoverBytes.byteLength; this.bits = new JSMpeg.BitBuffer(totalLength); this.bits.write([this.leftoverBytes, buffer]) } else { this.bits = new JSMpeg.BitBuffer(buffer) } while (this.bits.has(188 << 3) && this.parsePacket()) { } var leftoverCount = this.bits.byteLength - (this.bits.index >> 3); this.leftoverBytes = leftoverCount > 0 ? this.bits.bytes.subarray(this.bits.index >> 3) : null }; TS.prototype.parsePacket = function () { if (this.bits.read(8) !== 71) { if (!this.resync()) { return false } } var end = (this.bits.index >> 3) + 187; var transportError = this.bits.read(1), payloadStart = this.bits.read(1), transportPriority = this.bits.read(1), pid = this.bits.read(13), transportScrambling = this.bits.read(2), adaptationField = this.bits.read(2), continuityCounter = this.bits.read(4); var streamId = this.pidsToStreamIds[pid]; if (payloadStart && streamId) { var pi = this.pesPacketInfo[streamId]; if (pi && pi.currentLength) { this.packetComplete(pi) } } if (adaptationField & 1) { if (adaptationField & 2) { var adaptationFieldLength = this.bits.read(8); this.bits.skip(adaptationFieldLength << 3) } if (payloadStart && this.bits.nextBytesAreStartCode()) { this.bits.skip(24); streamId = this.bits.read(8); this.pidsToStreamIds[pid] = streamId; var packetLength = this.bits.read(16); this.bits.skip(8); var ptsDtsFlag = this.bits.read(2); this.bits.skip(6); var headerLength = this.bits.read(8); var payloadBeginIndex = this.bits.index + (headerLength << 3); var pi = this.pesPacketInfo[streamId]; if (pi) { var pts = 0; if (ptsDtsFlag & 2) { this.bits.skip(4); var p32_30 = this.bits.read(3); this.bits.skip(1); var p29_15 = this.bits.read(15); this.bits.skip(1); var p14_0 = this.bits.read(15); this.bits.skip(1); pts = (p32_30 * 1073741824 + p29_15 * 32768 + p14_0) / 9e4; this.currentTime = pts; if (this.startTime === -1) { this.startTime = pts } } var payloadLength = packetLength ? packetLength - headerLength - 3 : 0; this.packetStart(pi, pts, payloadLength) } this.bits.index = payloadBeginIndex } if (streamId) { var pi = this.pesPacketInfo[streamId]; if (pi) { var start = this.bits.index >> 3; var complete = this.packetAddData(pi, start, end); var hasPadding = !payloadStart && adaptationField & 2; if (complete || this.guessVideoFrameEnd && hasPadding) { this.packetComplete(pi) } } } } this.bits.index = end << 3; return true }; TS.prototype.resync = function () { if (!this.bits.has(188 * 6 << 3)) { return false } var byteIndex = this.bits.index >> 3; for (var i = 0; i < 187; i++) { if (this.bits.bytes[byteIndex + i] === 71) { var foundSync = true; for (var j = 1; j < 5; j++) { if (this.bits.bytes[byteIndex + i + 188 * j] !== 71) { foundSync = false; break } } if (foundSync) { this.bits.index = byteIndex + i + 1 << 3; return true } } } console.warn("JSMpeg: Possible garbage data. Skipping."); this.bits.skip(187 << 3); return false }; TS.prototype.packetStart = function (pi, pts, payloadLength) { pi.totalLength = payloadLength; pi.currentLength = 0; pi.pts = pts }; TS.prototype.packetAddData = function (pi, start, end) { pi.buffers.push(this.bits.bytes.subarray(start, end)); pi.currentLength += end - start; var complete = pi.totalLength !== 0 && pi.currentLength >= pi.totalLength; return complete }; TS.prototype.packetComplete = function (pi) { pi.destination.write(pi.pts, pi.buffers); pi.totalLength = 0; pi.currentLength = 0; pi.buffers = [] }; TS.STREAM = { PACK_HEADER: 186, SYSTEM_HEADER: 187, PROGRAM_MAP: 188, PRIVATE_1: 189, PADDING: 190, PRIVATE_2: 191, AUDIO_1: 192, VIDEO_1: 224, DIRECTORY: 255 }; return TS }(); JSMpeg.Decoder.Base = function () { "use strict"; var BaseDecoder = function (options) { this.destination = null; this.canPlay = false; this.collectTimestamps = !options.streaming; this.bytesWritten = 0; this.timestamps = []; this.timestampIndex = 0; this.startTime = 0; this.decodedTime = 0; Object.defineProperty(this, "currentTime", { get: this.getCurrentTime }) }; BaseDecoder.prototype.destroy = function () { }; BaseDecoder.prototype.connect = function (destination) { this.destination = destination }; BaseDecoder.prototype.bufferGetIndex = function () { return this.bits.index }; BaseDecoder.prototype.bufferSetIndex = function (index) { this.bits.index = index }; BaseDecoder.prototype.bufferWrite = function (buffers) { return this.bits.write(buffers) }; BaseDecoder.prototype.write = function (pts, buffers) { if (this.collectTimestamps) { if (this.timestamps.length === 0) { this.startTime = pts; this.decodedTime = pts } this.timestamps.push({ index: this.bytesWritten << 3, time: pts }) } this.bytesWritten += this.bufferWrite(buffers); this.canPlay = true }; BaseDecoder.prototype.seek = function (time) { if (!this.collectTimestamps) { return } this.timestampIndex = 0; for (var i = 0; i < this.timestamps.length; i++) { if (this.timestamps[i].time > time) { break } this.timestampIndex = i } var ts = this.timestamps[this.timestampIndex]; if (ts) { this.bufferSetIndex(ts.index); this.decodedTime = ts.time } else { this.bufferSetIndex(0); this.decodedTime = this.startTime } }; BaseDecoder.prototype.decode = function () { this.advanceDecodedTime(0) }; BaseDecoder.prototype.advanceDecodedTime = function (seconds) { if (this.collectTimestamps) { var newTimestampIndex = -1; var currentIndex = this.bufferGetIndex(); for (var i = this.timestampIndex; i < this.timestamps.length; i++) { if (this.timestamps[i].index > currentIndex) { break } newTimestampIndex = i } if (newTimestampIndex !== -1 && newTimestampIndex !== this.timestampIndex) { this.timestampIndex = newTimestampIndex; this.decodedTime = this.timestamps[this.timestampIndex].time; return } } this.decodedTime += seconds }; BaseDecoder.prototype.getCurrentTime = function () { return this.decodedTime }; return BaseDecoder }(); JSMpeg.Decoder.MPEG1Video = function () { "use strict"; var MPEG1 = function (options) { JSMpeg.Decoder.Base.call(this, options); this.onDecodeCallback = options.onVideoDecode; var bufferSize = options.videoBufferSize || 512 * 1024; var bufferMode = options.streaming ? JSMpeg.BitBuffer.MODE.EVICT : JSMpeg.BitBuffer.MODE.EXPAND; this.bits = new JSMpeg.BitBuffer(bufferSize, bufferMode); this.customIntraQuantMatrix = new Uint8Array(64); this.customNonIntraQuantMatrix = new Uint8Array(64); this.blockData = new Int32Array(64); this.currentFrame = 0; this.decodeFirstFrame = options.decodeFirstFrame !== false }; MPEG1.prototype = Object.create(JSMpeg.Decoder.Base.prototype); MPEG1.prototype.constructor = MPEG1; MPEG1.prototype.write = function (pts, buffers) { JSMpeg.Decoder.Base.prototype.write.call(this, pts, buffers); if (!this.hasSequenceHeader) { if (this.bits.findStartCode(MPEG1.START.SEQUENCE) === -1) { return false } this.decodeSequenceHeader(); if (this.decodeFirstFrame) { this.decode() } } }; MPEG1.prototype.decode = function () { var startTime = JSMpeg.Now(); if (!this.hasSequenceHeader) { return false } if (this.bits.findStartCode(MPEG1.START.PICTURE) === -1) { var bufferedBytes = this.bits.byteLength - (this.bits.index >> 3); return false } this.decodePicture(); this.advanceDecodedTime(1 / this.frameRate); var elapsedTime = JSMpeg.Now() - startTime; if (this.onDecodeCallback) { this.onDecodeCallback(this, elapsedTime) } return true }; MPEG1.prototype.readHuffman = function (codeTable) { var state = 0; do { state = codeTable[state + this.bits.read(1)] } while (state >= 0 && codeTable[state] !== 0); return codeTable[state + 2] }; MPEG1.prototype.frameRate = 30; MPEG1.prototype.decodeSequenceHeader = function () { var newWidth = this.bits.read(12), newHeight = this.bits.read(12); this.bits.skip(4); this.frameRate = MPEG1.PICTURE_RATE[this.bits.read(4)]; this.bits.skip(18 + 1 + 10 + 1); if (newWidth !== this.width || newHeight !== this.height) { this.width = newWidth; this.height = newHeight; this.initBuffers(); if (this.destination) { this.destination.resize(newWidth, newHeight) } } if (this.bits.read(1)) { for (var i = 0; i < 64; i++) { this.customIntraQuantMatrix[MPEG1.ZIG_ZAG[i]] = this.bits.read(8) } this.intraQuantMatrix = this.customIntraQuantMatrix } if (this.bits.read(1)) { for (var i = 0; i < 64; i++) { var idx = MPEG1.ZIG_ZAG[i]; this.customNonIntraQuantMatrix[idx] = this.bits.read(8) } this.nonIntraQuantMatrix = this.customNonIntraQuantMatrix } this.hasSequenceHeader = true }; MPEG1.prototype.initBuffers = function () { this.intraQuantMatrix = MPEG1.DEFAULT_INTRA_QUANT_MATRIX; this.nonIntraQuantMatrix = MPEG1.DEFAULT_NON_INTRA_QUANT_MATRIX; this.mbWidth = this.width + 15 >> 4; this.mbHeight = this.height + 15 >> 4; this.mbSize = this.mbWidth * this.mbHeight; this.codedWidth = this.mbWidth << 4; this.codedHeight = this.mbHeight << 4; this.codedSize = this.codedWidth * this.codedHeight; this.halfWidth = this.mbWidth << 3; this.halfHeight = this.mbHeight << 3; this.currentY = new Uint8ClampedArray(this.codedSize); this.currentY32 = new Uint32Array(this.currentY.buffer); this.currentCr = new Uint8ClampedArray(this.codedSize >> 2); this.currentCr32 = new Uint32Array(this.currentCr.buffer); this.currentCb = new Uint8ClampedArray(this.codedSize >> 2); this.currentCb32 = new Uint32Array(this.currentCb.buffer); this.forwardY = new Uint8ClampedArray(this.codedSize); this.forwardY32 = new Uint32Array(this.forwardY.buffer); this.forwardCr = new Uint8ClampedArray(this.codedSize >> 2); this.forwardCr32 = new Uint32Array(this.forwardCr.buffer); this.forwardCb = new Uint8ClampedArray(this.codedSize >> 2); this.forwardCb32 = new Uint32Array(this.forwardCb.buffer) }; MPEG1.prototype.currentY = null; MPEG1.prototype.currentCr = null; MPEG1.prototype.currentCb = null; MPEG1.prototype.pictureType = 0; MPEG1.prototype.forwardY = null; MPEG1.prototype.forwardCr = null; MPEG1.prototype.forwardCb = null; MPEG1.prototype.fullPelForward = false; MPEG1.prototype.forwardFCode = 0; MPEG1.prototype.forwardRSize = 0; MPEG1.prototype.forwardF = 0; MPEG1.prototype.deco

本文分享了如何使用JSMpeg在网页上播放WebSocket直流视频流的方法,包括引入JSMpeg库、配置播放器及遇到的性能瓶颈问题,实现在不同数量的视频墙上流畅播放视频。
&spm=1001.2101.3001.5002&articleId=121161232&d=1&t=3&u=9f657962a81143b89d155f5ae332b58d)

被折叠的 条评论
为什么被折叠?



