接收视频部分,然后使用javascript

时间:2018-07-25 16:17:02

标签: javascript video

我正在提供视频流服务,但无法在客户端上重新创建源流。

我制作了一个相机页面,将视频数据块和视频索引一起发送到服务器,然后服务器将该数据块存储在硬盘上,供客户端下载。我可以通过调用url从客户端检索视频数据块:

/Lessen/LesStreamPart/{streamid}?Index={index}

说明:

hub.server.join(current_lesid);

当客户端加入流时,页面将开始通过SignalR接收有关流的更新:

hub.client.updateLesStream = function (lesid, lesstreamid, contenttype, index, duration)

收到更新后,页面会检查它是否已经为该流设置了MediaSource控件(如果没有),因为这是第一次,因此页面将启动该流:

function startStream()

启动流后,页面将为视频元素设置MediaSource对象。然后等待MediaSource对象被实例化。

function openStream()

实例化MediaSource对象后,页面将开始使用Mimetype信息填充MediaSource对象,之后它将加载视频流的第一部分并将其附加到MediaSource对象。

function loadChunks()

MediaSource更新完成后,页面将开始加载剩余的视频部分。

hub.client.updateLesStream = function (lesid, lesstreamid, contenttype, index, duration)

当相机添加新块时,将使用SignalR再次发信号通知页面。由于流标识将与页面匹配,因此页面将继续通过调用以下内容来加载较新的块:

function loadChunks()

JS:

// Declare variables
var hub = $.connection.lesHub; // The SignalR hub

var buffering = false; // Semaphore for buffering
var video; // Pointer to video element
var mediaSource; // Pointer to mediasource object
var sourceBuffer; // Pointer to mediasource' sourcebuffer object

var current_lesid = document.querySelector('#LesId').value; // Current les id
var current_lesstreamid; // Current stream id (set in update)
var current_contenttype; // Current video content type (mimetype)
var current_index; // Current loaded index
var current_indexlength; // Current loaded index length    

// Will be called once SignalR sends a video chunk update event
function startStream() {

    // Open MediaSource
    mediaSource = new MediaSource();

    // Add listeners
    mediaSource.addEventListener('webkitsourceopen', openStream, false);
    //mediaSource.addEventListener('webkitsourceclose', closed, false);
    mediaSource.addEventListener('sourceopen', openStream, false);
    //mediaSource.addEventListener('sourceclose', closed, false);

    // Set MediaSource as video element source
    video = document.querySelector('video#VideoPlayerElement');
    video.src = URL.createObjectURL(mediaSource);

}
function openStream() {

    // Set the buffering semafore
    buffering = true;

    // Start the stream with contenttype
    sourceBuffer = mediaSource.addSourceBuffer(current_contenttype);

    // If there are any video chunks
    if (current_indexlength > 0) {

        // Load the first video chunk
        var url = "/Lessen/LesStreamPart/" + current_lesstreamid +"?Index=0";
        var req = new XMLHttpRequest();
        req.responseType = "arraybuffer";
        req.open("GET", url, true);
        req.onload = function () {

            // Append response to the sourcebuffer
            var resp = req.response;
            var array = new Uint8Array(resp);
            sourceBuffer.appendBuffer(array);

            // Set the current index to 0
            current_index = 0;

            // Wait for the sourcebuffer to be ready to load all other chunks
            sourceBuffer.addEventListener("updateend", loadChunks);

        }
        req.send();

    }
    else {

        // Release buffering semafore
        buffering = false;

    }
}
function loadChunks() {

    // Set the buffering semafore
    buffering = true;        

    // Calculate the newindex
    var newindex = current_index + 1;

    // Check if the newindex is in use?
    if (newindex < current_indexlength)
    {
        // Load new video chunk
        var url = "/Lessen/LesStreamPart/" + current_lesstreamid + "?Index=" + newindex;
        var req = new XMLHttpRequest();
        req.responseType = "arraybuffer";
        req.open("GET", url, true);
        req.onload = function () {

            // Append response to the sourcebuffer
            var resp = req.response;
            var array = new Uint8Array(resp);
            sourceBuffer.appendBuffer(array);

            // Set the current index to newindex
            current_index = newindex;

            // Recursive call to add remaining chunks
            loadChunks();

        }
        req.send();

    }
    else {

        // Newindex is not in use, release buffering semafore
        buffering = false;

    }

}


// Start recording callbacks
hub.client.startLesStream = function (lesid, lesstreamid, contenttype) {

    // This is called while there are no video data chunks, so we can ignore it.

};

// Update recording callbacks
hub.client.updateLesStream = function (lesid, lesstreamid, contenttype, index, duration) {

    // Check if update is for our lesid (not actually needed)
    if (current_lesid == lesid) {

        // Check if buffering
        if (buffering) {

            // The webpage is currently busy, we will time out this message with 100ms
            setTimeout(function () {
                hub.client.updateLesStream(lesid, lesstreamid, contenttype, index, duration);
            }, 100);

        }
        else {

            // Not buffering, so we can begin processing
            // When the streamid is different reload the stream, when the page starts 
            // the "current_lesstreamid" is undefined, so we will reload the video
            if (current_lesstreamid == lesstreamid) {

                // Update to current stream
                current_indexlength = index + 1;
                loadChunks();

            }
            else {

                // Different stream started
                current_lesstreamid = lesstreamid;
                current_contenttype = contenttype;
                current_indexlength = index + 1;
                startStream();

            }

        }

    }

};

// Stop recording callbacks
hub.client.stopLesStream = function (lesid, lesstreamid, contenttype) {

    // Check if update is for our lesid (not actually needed)
    if (current_lesid == lesid) {

        // Check if stream is currently shown
        if (current_lesstreamid == lesstreamid) {

            // Stop the stream
            mediaSource.endOfStream();

        }

    }

};


// Start SignalR
$.connection.hub.start().done(function () {

    // And join the room
    hub.server.join(current_lesid);

});

HTML:

<input type="hidden" id="LesId" value="@(Model.Id)" />
<video autoplay controls id="VideoPlayerElement"></video>

输出:

Broken video controls

该页面没有显示任何错误,但是我在video元素中确实看到一个损坏的视频图标。有谁知道这可能是什么?

我在另一个stackoverflow中读到,可能需要使用VP8编解码器,我对其进行了更改,但是仍然无法正常工作。

编辑:

我稍微更改了JavaScript代码。原来我调用了“ loadChunks”函数,但“ sourceBuffer”的“ updateend”事件已经调用了该函数。然后,我有很多错误。

我将与服务器通信的方式更改为“ $ .get();”。它解决了错误,但是我仍然没有图像。

// Declare variables
var hub = $.connection.lesHub; // The SignalR hub

var buffering = false; // Semaphore for buffering
var video; // Pointer to video element
var mediaSource; // Pointer to mediasource object
var sourceBuffer; // Pointer to mediasource' sourcebuffer object

var current_lesid = document.querySelector('#LesId').value; // Current les id
var current_lesstreamid; // Current stream id (set in update)
var current_contenttype; // Current video content type (mimetype)
var current_index; // Current loaded index
var current_indexlength; // Current loaded index length    

// Will be called once SignalR sends a video chunk update event
function startStream() {

    // Open MediaSource
    mediaSource = new MediaSource();

    // Add listeners
    mediaSource.addEventListener('webkitsourceopen', openStream, false);
    //mediaSource.addEventListener('webkitsourceclose', closed, false);
    mediaSource.addEventListener('sourceopen', openStream, false);
    //mediaSource.addEventListener('sourceclose', closed, false);

    // Set MediaSource as video element source
    video = document.querySelector('video#VideoPlayerElement');
    video.src = URL.createObjectURL(mediaSource);

}
function openStream() {

    // Set the buffering semafore
    buffering = true;

    // Start the stream with contenttype
    sourceBuffer = mediaSource.addSourceBuffer(current_contenttype);

    // Wait for the sourcebuffer to be ready to load all other chunks
    sourceBuffer.addEventListener("updateend", loadChunks);

    // If there are any video chunks
    if (current_indexlength > 0) {

        // Load the first video chunk
        var url = "/Lessen/LesStreamPart/" + current_lesstreamid + "?Index=0";

        //$("body").append("<video><source src='" + url + "'/></video>");

        $.get(url, function (resp) {

            //var req = new XMLHttpRequest();
            //req.responseType = "arraybuffer";
            //req.open("GET", url, true);
            //req.onload = function () {

            // Append response to the sourcebuffer
            //var resp = req.response;
            var array = new Uint8Array(resp);
            sourceBuffer.appendBuffer(array);

            // Set the current index to 0
            current_index = 0;

            //}
            //req.send();

        });
    }
    else {

        // Release buffering semafore
        buffering = false;

    }
}
function loadChunks() {

    //video.play();

    // Set the buffering semafore
    buffering = true;        

    // Calculate the newindex
    var newindex = current_index + 1;

    // Check if the newindex is in use?
    if (newindex < current_indexlength) {
        // Load new video chunk
        var url = "/Lessen/LesStreamPart/" + current_lesstreamid + "?Index=" + newindex;

        $.get(url, function (resp) {

            //var req = new XMLHttpRequest();
            //req.responseType = "arraybuffer";
            //req.open("GET", url, true);
            //req.onload = function () {

            // Append response to the sourcebuffer
            //var resp = req.response;
            var array = new Uint8Array(resp);
            sourceBuffer.appendBuffer(array);

            // Set the current index to newindex
            current_index = newindex;

            //}
            //req.send();

        });
    }
    else {

        // Newindex is not in use, release buffering semafore
        buffering = false;

    }

}


// Start recording callbacks
hub.client.startLesStream = function (lesid, lesstreamid, contenttype) {

    // This is called while there are no video data chunks, so we can ignore it.

};

// Update recording callbacks
hub.client.updateLesStream = function (lesid, lesstreamid, contenttype, index, duration) {

    // Check if update is for our lesid (not actually needed)
    if (current_lesid == lesid) {

        // Check if buffering
        if (buffering) {

            // The webpage is currently busy, we will time out this message with 100ms
            setTimeout(function () {
                hub.client.updateLesStream(lesid, lesstreamid, contenttype, index, duration);
            }, 100);

        }
        else {

            // Not buffering, so we can begin processing
            // When the streamid is different reload the stream, when the page starts 
            // the "current_lesstreamid" is undefined, so we will reload the video
            if (current_lesstreamid == lesstreamid) {

                // Update to current stream
                current_indexlength = index + 1;
                loadChunks();

            }
            else {

                // Different stream started
                current_lesstreamid = lesstreamid;
                current_contenttype = contenttype;
                current_indexlength = index + 1;
                startStream();

            }

        }

    }

};

// Stop recording callbacks
hub.client.stopLesStream = function (lesid, lesstreamid, contenttype) {

    // Check if update is for our lesid (not actually needed)
    if (current_lesid == lesid) {

        // Check if stream is currently shown
        if (current_lesstreamid == lesstreamid) {

            // Stop the stream
            mediaSource.endOfStream();

        }

    }

};


// Start SignalR
$.connection.hub.start().done(function () {

    // And join the room
    hub.server.join(current_lesid);

});

2 个答案:

答案 0 :(得分:0)

我有一个完美的例子来解决这个问题,并以简单的方式...

我正在使用三个静态文件,但您也可以从套接字或任何api附加数据。

<!DOCTYPE html>
<html>

<head>
</head>

<body>
  <br>
  <video controls="true" autoplay="true"></video>

  <script>
    (async() => {


      const mediaSource = new MediaSource();

      const video = document.querySelector("video");

      // video.oncanplay = e => video.play();

      const urls = ["https://nickdesaulniers.github.io/netfix/demo/frag_bunny.mp4", "https://raw.githubusercontent.com/w3c/web-platform-tests/master/media-source/mp4/test.mp4","https://nickdesaulniers.github.io/netfix/demo/frag_bunny.mp4"];

      const request = url => fetch(url).then(response => response.arrayBuffer());

      // `urls.reverse()` stops at `.currentTime` : `9`
      const files = await Promise.all(urls.map(request));

      /*
       `.webm` files
       Uncaught DOMException: Failed to execute 'appendBuffer' on 'SourceBuffer': This SourceBuffer has been removed from the parent media source.
       Uncaught DOMException: Failed to set the 'timestampOffset' property on 'SourceBuffer': This SourceBuffer has been removed from the parent media source.
      */
      // const mimeCodec = "video/webm; codecs=opus";
      // https://stackoverflow.com/questions/14108536/how-do-i-append-two-video-files-data-to-a-source-buffer-using-media-source-api/
      const mimeCodec = "video/mp4; codecs=avc1.42E01E, mp4a.40.2";


      const media = await Promise.all(files.map(file => {
        return new Promise(resolve => {
          let media = document.createElement("video");
          let blobURL = URL.createObjectURL(new Blob([file]));
          media.onloadedmetadata = async e => {
            resolve({
              mediaDuration: media.duration,
              mediaBuffer: file
            })
          }
          media.src = blobURL;
        })
      }));

      console.log(media);

      mediaSource.addEventListener("sourceopen", sourceOpen);

      video.src = URL.createObjectURL(mediaSource);

      async function sourceOpen(event) {

        if (MediaSource.isTypeSupported(mimeCodec)) {
          const sourceBuffer = mediaSource.addSourceBuffer(mimeCodec);

          for (let chunk of media) {
            await new Promise(resolve => {
              sourceBuffer.appendBuffer(chunk.mediaBuffer);
              sourceBuffer.onupdateend = e => {
                sourceBuffer.onupdateend = null;
                sourceBuffer.timestampOffset += chunk.mediaDuration;
                console.log(mediaSource.duration);
                resolve()
              }
            })

          }

          mediaSource.endOfStream();

        }  
        else {
          console.warn(mimeCodec + " not supported");
        }
      };

    })()
  </script>


</body>

</html>

答案 1 :(得分:0)

这似乎是一个编解码器问题和读取数据的方法。当您收到视频 blob 时,您需要使用 FileReader 转换/存储它,这对我有用。为了获得最佳编解码器支持,我需要使用 VP8 编解码器(如果您知道更好的编解码器,请告诉我)。

这是我的工作示例,我使用 MediaRecorder 记录网络摄像头,然后将视频块粘贴到 MediaSource 中。

const video1 = document.getElementById('video1');
        const video2 = document.getElementById('video2');

        const mediaSource = new MediaSource();
        video2.src = URL.createObjectURL(mediaSource);
        mediaSource.addEventListener('sourceopen', sourceOpen);

        function sourceOpen(openargs) {
            navigator.mediaDevices
                .getUserMedia({ audio: false, video: true })
                .then(function (stream) {
                    video1.srcObject = stream;

                    var options = { mimeType: 'video/webm; codecs=vp8' };
                    var mediaRecorder = new MediaRecorder(stream, options);
                    var sourceBuffer = null;

                    mediaRecorder.ondataavailable = function (e) {
                        if (sourceBuffer == null) {
                            sourceBuffer = mediaSource.addSourceBuffer(mediaRecorder.mimeType);
                            window.sourceBuffer = sourceBuffer;
                        }
                        var reader = new FileReader();
                        reader.addEventListener("loadend", function () {
                            var arr = new Uint8Array(reader.result);
                            sourceBuffer.appendBuffer(arr);
                        });
                        reader.readAsArrayBuffer(e.data);
                    };
                    mediaRecorder.start(5000);
                });
        }
        video {
            width: 320px;
            height: 180px;
        }
<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8" />
    <title></title>
</head>
<body>

    <video id="video1" controls autoplay muted></video><br />
    <video id="video2" controls autoplay muted></video>
</body>
</html>