zoukankan      html  css  js  c++  java
  • websocket搭建简单的H264实时视频流播放

    websocket搭建简单的H264实时视频流播放

    为了方便引入了vuejs,跟element-ui,

    <!DOCTYPE html>
    <html lang="en">

    <head>
    <meta charset="UTF-8">
    <title>hs-demo</title>
    <!-- 引入样式 -->
    <link rel="stylesheet" href="https://unpkg.com/element-ui/lib/theme-chalk/index.css">
    <style>
    .el-main {
    display: flex;
    100%;
    flex-wrap: wrap;
    justify-content: space-between;
    }
    .video-box {
    400px;
    height: 500px;
    border-radius: 4px;
    border: 1px solid gray;
    margin-bottom: 20px;
    }
    .video-main {
    100%;
    }
    .video-info {
    padding: 5px;
    }
    .el-input {margin-bottom: 5px}
    .video-btn .el-button {
    margin-right: 5px;
    }
    </style>
    </head>

    <body>
    <div id="app">
    <el-button style="margin: 20px 0 0 20px;" @click="addPlayer">Add+</el-button>
    <el-main>
    <div v-for="(item, index) in videoList" class="video-box">
    <div class="video-main">
    <video width="100%" height="330px" controls autoplay :id="item.el"></video>
    </div>
    <div class="video-info">
    <el-input placeholder="输入视频地址" v-model="item.assetURL">
    <template slot="prepend">视频地址</template>
    </el-input>
    <div class="video-btn">
    <el-button v-show="!item.isPlaying" :loading="item.videoLoading" @click="wsInit(item)">play</el-button>
    <el-button v-show="item.isPlaying" @click="wsDestroy(item)">destory</el-button>
    <el-button @click="wsRemove(index)">remove</el-button>
    </div>
    </div>
    </div>
    </el-main>
    </div>

    <script src="./vue.js"></script>
    <script src="https://unpkg.com/element-ui/lib/index.js"></script>
    <script>
    new Vue({
    el: '#app',
    data: {
    videoList: [],
    loading: null,
    },
    mounted() {
    this.addPlayer();
    },
    methods: {
    addPlayer() {
    let obj = {
    el: `video${Math.random().toString().slice(2,9)}`, // 播放器dom
    assetURL: 'ws://10.116.64.179:20185/real/sub/2fa2f2d8cc6be4d02f92994a5933674f/4325031985_581.mp4',
    seq: 1,
    mimeCodec: null,
    mediaSource: null,
    sourceBuffer: null,
    isPlaying: false,
    isReady: true,
    queue: [],
    ws: null,
    videoLoading: false
    }
    this.videoList.push(obj);
    },
    wsInit(item) {
    item.videoLoading = true;
    this.loading = this.$loading({
    target: '.video-main',
    lock: true,
    text: 'Loading',
    spinner: 'el-icon-loading',
    background: 'rgba(0, 0, 0, 0.7)'
    });
    item.ws = new WebSocket(item.assetURL);
    item.ws.binaryType = 'arraybuffer';
    item.ws.onopen = () => {
    item.ws.send(JSON.stringify({method:'open', seq: item.seq}))
    };
    item.ws.onerror = function () {

    };
    item.ws.onmessage = (ev) => {
    let {data} = ev;
    if (data instanceof ArrayBuffer) {
    item.queue.push(data);
    if (!item.isPlaying) {
    this.initPlayer(item);
    } else {
    this.feed(item);
    }
    } else {
    let mes = JSON.parse(ev.data)
    if (mes.method === 'open') {
    item.mimeCodec = mes.mime;
    item.ws.send(JSON.stringify({method:'play', seq: item.seq}))
    }
    }
    };
    item.ws.onclose = function () {
    console.log(item.el);
    };
    },
    initPlayer(item) {
    if ('MediaSource' in window && MediaSource.isTypeSupported(item.mimeCodec)) {
    item.isPlaying = true;
    item.mediaSource = new MediaSource();
    item.video = document.getElementById(`${item.el}`);
    item.video.src = URL.createObjectURL(item.mediaSource);
    item.mediaSource.addEventListener('sourceopen', this.sourceOpen.bind(null, item));
    } else {
    console.error('Unsupported MIME type or codec: ', item.mimeCodec);
    }
    },
    sourceOpen(item) {
    // 存在了sourceBuffer 不再重复生成
    item.sourceBuffer = item.mediaSource.addSourceBuffer(item.mimeCodec);
    item.sourceBuffer.mode = 'sequence';
    item.sourceBuffer.addEventListener('updateend', () => {
    if (!item.isPlaying) {
    item.video.play();
    }
    // 判断如果队列里有数据则继续放入sourceBuffer
    item.isReady = true;
    });
    this.feed(item);
    },
    feed(item) {
    if (item.isReady && item.queue.length) {
    item.videoLoading = false;
    this.loading.close();
    item.isReady = false;
    item.sourceBuffer.appendBuffer(item.queue.shift());
    }
    },
    wsDestroy(item) {
    item.isPlaying = false;
    item.ws.send(JSON.stringify({method:'close', seq: item.seq}));
    item.ws.close();
    this.wsReset(item);
    },
    wsReset(item) {
    Object.assign(item, {
    mimeCodec: null,
    mediaSource: null,
    sourceBuffer: null,
    isPlaying: false,
    isReady: true,
    queue: [],
    ws: null,
    videoLoading: false
    })
    },
    wsRemove(index) {
    this.videoList.splice(index, 1)
    }
    }
    })
    </script>
    </body>
    </html>

    连接websocket跟获取视频流的地址采用的同一个,也就是说每个视频流播放都会重新实例化一个websocket,
    原理就是利用了mideaSource流式加载视频资源,
    c++通过websocket不断的推二进制数据流过来,然后喂到video里面,
    注意的是,sourceBuffer在上一个chunk updateend之前不能加入新的chunk,否则会报错。
  • 相关阅读:
    IIS中安全证书更新
    mac权限相关命令
    Nlog配置Seq日志服务
    WPF Prism8.0中注册Nlog日志服务
    .NET MAUI Preview7 状态预览(8月)
    RPA剖析浏览器API(获取指定页面数据)
    .NET Core Worker Service
    .NET MAUI Preview6 状态预览(7月)
    Digicert GeoTrusy Cloud DV
    【Tomcat8】开启Https及挂载静态文件
  • 原文地址:https://www.cnblogs.com/hsdying/p/14207844.html
Copyright © 2011-2022 走看看