123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349 |
- <template>
- <div class="disk-video">
- <video
- v-if="hasMediaSource"
- ref="video"
- class="video"
- autoplay
- muted
- controls
- ></video>
- <canvas
- v-else
- class="canvas"
- width="720"
- height="1280"
- ></canvas>
- </div>
- </template>
- <script>
- import JMuxer from 'jmuxer';
- import qs from 'qs';
- // bcc校验码计算
- // arry: 要计算的数组
- // 返回计算协议中校验位的校验码
- function calBcc(arry) {
- let bcc = 0;
- for (let i = 0; i < arry.length; i++) {
- bcc ^= arry[i];
- }
- return bcc;
- }
- function makeFrameExtend(sn, dataType, body) {
- let index = 0;
- const dataLen = body.length;
- const frameLen = dataLen + 26;
- const outPut = new Uint8Array(frameLen);
- outPut[index++] = 0x68;
- outPut[index++] = (dataLen & 0xff000000) >> 24;
- outPut[index++] = (dataLen & 0x00ff0000) >> 16;
- outPut[index++] = (dataLen & 0x0000ff00) >> 8;
- outPut[index++] = dataLen & 0x000000ff;
- outPut[index++] = 0; // 类型为client
- // sn号赋值,string转ascii
- for (let i = 0; i < sn.length; i++) {
- outPut[index++] = sn[i].charCodeAt();
- }
- outPut[index++] = dataType; // 指定数据类型为json
- // json string转ascii
- for (let i = 0; i < body.length; i++) {
- outPut[index++] = body[i];
- }
- const bccBuffer = outPut.slice(1, frameLen - 3 + 1); // 忽略协议头和协议尾
- outPut[index++] = calBcc(bccBuffer);
- outPut[index++] = 0x16;
- return outPut;
- }
- // 生成鉴权报文
- function VerifyCode(sn, code) {
- const len = code.length + 1;
- const codeBuffer = new TextEncoder('utf-8').encode(code); // 获取字符串ascii码
- const buffer = new Uint8Array(len);
- buffer[0] = 0x04;
- for (let i = 0; i < codeBuffer.length; i++) {
- buffer[i + 1] = codeBuffer[i];
- }
- return makeFrameExtend(sn, 6, buffer);
- }
- // I 帧请求报文生成
- function RequestIFrame(sn) {
- // let sn = "RK3923C1201900139";
- const outPut = new Uint8Array([0x20]);
- return makeFrameExtend(sn, 6, outPut);
- }
- // 数组打印,调试用
- function PrintArry(data) {
- let str = '';
- for (let i = 0; i < data.length; i++) {
- str = str + data[i].toString(16).padStart(2, '0');
- }
- str = str.toUpperCase();
- return str;
- }
- // 检查鉴权报文
- function CheckVerifyCode(data) {
- const dataLen = data.length - 26;
- const body = data.slice(24, 24 + dataLen);
- console.log('打印:' + PrintArry(body));
- if (body[3] === 0x03) {
- return true;
- }
- return false;
- }
- // 通道配置
- function ConfigChannel(sn) {
- const outPut = new Uint8Array([0x07]);
- return makeFrameExtend(sn, 6, outPut);
- }
- // 查询屏幕方向
- function GetScreenState(sn) {
- // let sn = "RK3923C1201900139";
- const outPut = new Uint8Array([
- 0x00, 0x00, 0x00, 0x01, 0x01, 0x01, 0x01, 0x02,
- ]);
- return makeFrameExtend(sn, 5, outPut);
- }
- // 根据报文识别屏幕方向, 0横屏,1竖屏
- function CheckScreenDirection(data) {
- if (data[0] === 0 && data[1] === 0 && data[2] === 0 && data[3] === 1) {
- if (data[4] === 1 && data[5] === 1) {
- if (data[6] === 1) {
- const screen = data[7];
- return screen;
- }
- }
- }
- }
- // 多端登录数据解析
- function checkMultiLoginInfo(input) {
- const dataLen = input.length - 26; // 得到json 长度
- const jsonHex = input.slice(24, 24 + dataLen); // 截取json hex二进制数据
- const jsonStr = new TextDecoder('utf-8').decode(jsonHex);
- console.log('取得json 字符串:' + jsonStr);
- const jsonObj = JSON.parse(jsonStr);
- return jsonObj;
- }
- // 切换清晰度
- function makeSharpness(sn, level) {
- // var sn = "RK3923C1201900139";
- const jsonObj = {
- type: 2,
- data: { definition: level, clientType: 'h5', sceneType: 'cloudPhone' },
- };
- const jsonStr = JSON.stringify(jsonObj);
- const outPut = new TextEncoder('utf-8').encode(jsonStr);
- return makeFrameExtend(sn, 0xd, outPut);
- }
- export default {
- name: 'DiskVideo',
- props: {
- info: {
- type: Object,
- default: null,
- },
- },
- data() {
- return {
- hasMediaSource: false,
- };
- },
- async fetch() {
- this.info && (await this.initPlugflowWebSocket());
- },
- computed: {
- sn() {
- return this.info.sn;
- },
- },
- watch: {
- info: '$fetch',
- },
- created() {},
- mounted() {
- this.initVideo();
- },
- methods: {
- initPlugflowWebSocket() {
- this?._ws?.close();
- const { internetHttps, localIp, sn, cardToken } = this.info;
- const url = `wss://${internetHttps}/plugflow${qs.stringify(
- {
- cardIp: localIp,
- token: cardToken,
- type: 'business',
- },
- { addQueryPrefix: true },
- )}`;
- const ws = new WebSocket(url);
- ws.binaryType = 'arraybuffer';
- ws.addEventListener('open', (e) => {
- ws.send('ping');
- ws._pingInterval = setInterval(() => {
- ws.send('ping');
- }, 1000 * 1);
- ws.send(VerifyCode(sn, cardToken));
- // ws.send(makeSharpness(sn, 4));
- // ws.send(RequestIFrame(sn));
- });
- ws.addEventListener('error', (e) => {});
- ws.addEventListener('message', (event) => {
- const ParseProto = (data) => {
- const input = new Uint8Array(data);
- let duration;
- let video;
- let frameType;
- let audio;
- if (
- input[0] === 0 &&
- input[1] === 0 &&
- input[2] === 0 &&
- input[3] === 1
- ) {
- video = input;
- duration = 24;
- const nalType = input[4] & 0x1f;
- frameType = nalType;
- // if (!isFeed) {
- // if (nalType == 0x05 && isVisuable) {
- // isFeed = true;
- // }
- // }
- } else if (input[0] === 0xff) {
- audio = input;
- duration = 24;
- } else if (input[0] === 0x68) {
- if (input[23] === 0x5c) {
- console.log('收到消息:' + PrintArry(input));
- if (CheckVerifyCode(input)) {
- ws.send(ConfigChannel(sn));
- // ws.send(GetScreenState(sn));
- } else {
- // connect('update');
- }
- }
- if (input[23] === 0x05) {
- // 横竖屏标识
- const state = CheckScreenDirection(input.slice(24, 24 + 8));
- if (state === 1) {
- console.log('安卓卡此时竖屏');
- // 竖屏处理
- // resolving = 1;
- } else {
- console.log('安卓卡此时横屏');
- // 横屏处理
- // resolving = 0;
- }
- }
- if (input[23] === 0x0b) {
- // 多端登录处理, 数据从索引24开始取, input 是接收到的原始数据
- const jsonobj = checkMultiLoginInfo(input);
- // console.log(
- // '🚀 ~ file: disk.vue ~ line 324 ~ ParseProto ~ jsonobj',
- // jsonobj,
- // );
- }
- }
- return {
- audio,
- video,
- duration,
- frameType,
- };
- };
- const data = ParseProto(event.data); // JAVA服务器转发
- // console.log(
- // '🚀 ~ file: disk.vue ~ line 336 ~ ws.addEventListener ~ data',
- // data,
- // );
- if (data.video) {
- this.pushVideo(data);
- }
- });
- ws.addEventListener('close', (event) => {
- clearInterval(event.currentTarget._pingInterval);
- });
- this.$once('hook:beforeDestroy', () => {
- ws.close();
- });
- this._ws = ws;
- },
- initVideo() {
- this.hasMediaSource = !!window.MediaSource;
- // this.hasMediaSource = false;
- if (this.hasMediaSource) {
- this._jmuxer = new JMuxer({
- node: this.$refs.video,
- flushingTime: 33,
- fps: 30,
- mode: 'video',
- debug: true,
- onReady :()=>{
- console.log('JMuxer ready');
- }
- });
- } else {
- throw new Error('当前设备不支持MediaSource');
- }
- },
- pushVideo(data) {
- console.log("🚀 ~ file: video.vue ~ line 315 ~ pushVideo ~ data", data)
- if (this.hasMediaSource) {
- this?._jmuxer?.feed({
- video: data.video,
- // duration: data.duration,
- // frameType: data.frameType,
- });
- this.$refs?.video?.play();
- }
- },
- },
- };
- </script>
- <style lang="scss" scoped>
- .disk-video {
- position: absolute;
- top: 0;
- left: 0;
- right: 0;
- bottom: 0;
- z-index: 0;
- }
- .video {
- position: relative;
- width: 100%;
- height: 100%;
- object-fit: fill;
- }
- .canvas {
- position: relative;
- width: 100%;
- height: 100%;
- }
- </style>
|