Jak nagrywać kamerę internetową i dźwięk za pomocą webRTC i połączenia równorzędnego na serwerze

90

Chciałbym nagrać kamerę internetową i dźwięk użytkownika i zapisać do pliku na serwerze. Te pliki mogłyby być następnie udostępniane innym użytkownikom.

Nie mam problemów z odtwarzaniem, ale mam problemy z nagraniem zawartości.

Rozumiem, że .record()funkcja getUserMedia nie została jeszcze napisana - do tej pory złożono tylko propozycję.

Chciałbym utworzyć połączenie równorzędne na moim serwerze za pomocą PeerConnectionAPI. Rozumiem, że to trochę hakerskie, ale myślę, że powinno być możliwe utworzenie peera na serwerze i zarejestrowanie tego, co wysyła klient-peer.

Jeśli jest to możliwe, powinienem być w stanie zapisać te dane do flv lub innego formatu wideo.

W rzeczywistości wolę nagrywać kamerę internetową + dźwięk po stronie klienta, aby umożliwić klientowi ponowne nagrywanie filmów, jeśli nie podobała mu się pierwsza próba przed przesłaniem. Pozwoliłoby to również na przerwy w połączeniach sieciowych. Widziałem kod, który umożliwia nagrywanie poszczególnych „obrazów” z kamery internetowej poprzez wysyłanie danych do kanwy - to fajne, ale potrzebuję też dźwięku.

Oto kod po stronie klienta, który mam do tej pory:

  <video autoplay></video>

<script language="javascript" type="text/javascript">
function onVideoFail(e) {
    console.log('webcam fail!', e);
  };

function hasGetUserMedia() {
  // Note: Opera is unprefixed.
  return !!(navigator.getUserMedia || navigator.webkitGetUserMedia ||
            navigator.mozGetUserMedia || navigator.msGetUserMedia);
}

if (hasGetUserMedia()) {
  // Good to go!
} else {
  alert('getUserMedia() is not supported in your browser');
}

window.URL = window.URL || window.webkitURL;
navigator.getUserMedia  = navigator.getUserMedia || navigator.webkitGetUserMedia ||
                          navigator.mozGetUserMedia || navigator.msGetUserMedia;

var video = document.querySelector('video');
var streamRecorder;
var webcamstream;

if (navigator.getUserMedia) {
  navigator.getUserMedia({audio: true, video: true}, function(stream) {
    video.src = window.URL.createObjectURL(stream);
    webcamstream = stream;
//  streamrecorder = webcamstream.record();
  }, onVideoFail);
} else {
    alert ('failed');
}

function startRecording() {
    streamRecorder = webcamstream.record();
    setTimeout(stopRecording, 10000);
}
function stopRecording() {
    streamRecorder.getRecordedData(postVideoToServer);
}
function postVideoToServer(videoblob) {
/*  var x = new XMLHttpRequest();
    x.open('POST', 'uploadMessage');
    x.send(videoblob);
*/
    var data = {};
    data.video = videoblob;
    data.metadata = 'test metadata';
    data.action = "upload_video";
    jQuery.post("http://www.foundthru.co.uk/uploadvideo.php", data, onUploadSuccess);
}
function onUploadSuccess() {
    alert ('video uploaded');
}

</script>

<div id="webcamcontrols">
    <a class="recordbutton" href="javascript:startRecording();">RECORD</a>
</div>
Dave Hilditch
źródło
Mam ten sam problem. Czy metoda getRecordedData () działa dla Ciebie? Nie ma go w moich świeżo zaktualizowanych przeglądarkach.
Firas
Nie - próbowałem też „Google Canary”.
Dave Hilditch
Tak, uważnie go obserwuję - zaktualizuję ten wątek, gdy znajdzie się odpowiednie rozwiązanie.
Dave Hilditch
2
jeśli masz rozwiązanie powyższego pytania, podziel się ze mną, Dziękuję
Muhammad
2
Czy ktoś był w stanie uzyskać dostęp do bajtów MediaStream za pośrednictwem magii RTC po stronie serwera?
Vinay

Odpowiedzi:

44

Zdecydowanie powinieneś rzucić okiem na Kurento . Zapewnia infrastrukturę serwera WebRTC, która umożliwia nagrywanie z kanału WebRTC i wiele więcej. Można również znaleźć kilka przykładów zastosowania planujesz tutaj . Naprawdę łatwo jest dodać możliwości nagrywania do tej demonstracji i przechowywać plik multimedialny w URI (dysk lokalny lub gdziekolwiek).

Projekt jest objęty licencją LGPL Apache 2.0


EDYCJA 1

Od tego postu dodaliśmy nowy samouczek, który pokazuje, jak dodać rejestrator w kilku scenariuszach

Zastrzeżenie: jestem częścią zespołu, który rozwija Kurento.

igracia
źródło
2
@Redtopia W niektórych ostatnich testach obciążenia udało nam się uzyskać 150 połączeń one2one webrtc na pamięci RAM i5 / 16GB. Możesz spodziewać się, że te liczby będą lepsze w przyszłości, ale nie spodziewaj się cudów: w przypadku SRTP odbywa się dużo szyfrowania, a to jest wymagające. Przyglądamy się szyfrowaniu / deszyfrowaniu przyspieszanemu sprzętowo, a liczby będą
rosły
2
@ user344146 To prawdopodobnie ja odpowiadałem. Czy mógłbyś udostępnić link do tego posta? Jeśli otrzymałeś tę odpowiedź, to prawdopodobnie dlatego, że zapytałeś o coś, co już tam było lub na liście. Wygląda na to, że próbujesz skompilować wersję SNAPSHOT. Te artefakty nie są publikowane w centralnej wersji, więc możesz pobrać wersję samouczków lub skorzystać z naszego wewnętrznego repozytorium deweloperów. Wiele razy odpowiadano na to na liście, w dokumentacji znajduje się wpis dotyczący pracy z wersjami deweloperskimi ... Poświęciliśmy trochę czasu, aby go napisać, więc byłoby miło z Twojej strony, abyś poświęcił czas na przeczytanie.
igracia
2
Po prostu używam Kurento do zrobienia takiego nagrania. Nie jest to skomplikowane, ale potrzebuję trochę czasu, aby zrozumieć koncepcję - ponieważ niektóre dokumenty są naprawdę wredne - i znalezienie tego, co mogę wysłać do kurento, lub opis wydarzeń i tak dalej, może być czasami naprawdę frustrujące. W każdym razie - taki otwarty projekt to naprawdę świetna robota i warto z niej skorzystać. Kurento działa tylko w systemie Linux (wersja dla systemu Windows nie jest oficjalna i nie działa z pełną funkcjonalnością).
Krystian
1
Znalazłem odpowiedzi na powyższe pytania (publikowanie tutaj dla innych), Kurento obecnie obsługuje JDK 7.0, Nie oznacza to, że musi być zależne od Ubuntu 14.04, powinno również obsługiwać późniejsze wersje, ale Kurento nie jest oficjalnie testowane na innych wersjach Ubuntu / inna wersja Linuksa. Również Kurento wydaje wersje 64-bitowe jako łatwo dostępne do instalacji, jednak możesz zainstalować 32-bitową wersję serwera, ale najpierw musisz ją zbudować.
Bilbo Baggins
1
Niestety, jak stwierdziłem w mojej odpowiedzi, rozwój Kurento poważnie spowolnił po przejęciu Twilio. Zamiast tego polecam używanie Janusa.
jamix,
17

Proszę sprawdzić RecordRTC

RecordRTC jest licencjonowane przez MIT na github .

Dmitry
źródło
2
To jest całkiem niesamowite - moje pytanie: czy to może nagrywać wideo i audio razem (na żywo prawdziwy film, a nie dwie oddzielne rzeczy?)
Brian Dear
Zgoda - super, ale wygląda na to, że rejestruje dane tylko osobno.
Dave Hilditch
3
@BrianDear, jest jeden RecordRTC-together
Mifeng
2
To podejście działa przez Whammy.js w Chrome. Jest to problematyczne, ponieważ jakość jest znacznie niższa w porównaniu z emulacją Whammy z powodu braku MediaStreamRecorder w Chrome. Zasadniczo dzieje się tak, że WhammyRecorder wskazuje tag wideo na adres URL obiektu MediaStream, a następnie wykonuje migawki elementu Canvas z określoną liczbą klatek na sekundę. Następnie używa Whammy do umieszczenia wszystkich tych ramek w wideo webm.
Vinay
15

Uważam za pomocą kurento lub innych mikrokontrolerów tylko do nagrywania filmów byłoby trochę przesadą, zwłaszcza biorąc pod uwagę fakt, że Chrome ma MediaRecorder API wsparcie od V47 i Firefox od V25. Więc na tym skrzyżowaniu możesz nawet nie potrzebować zewnętrznej biblioteki js do wykonania pracy, wypróbuj to demo, które zrobiłem, aby nagrywać wideo / audio za pomocą MediaRecorder:

Demo - działałoby w Chrome i Firefox (celowo pominięto wypychanie obiektu blob do kodu serwera)

Źródło kodu Github

Jeśli korzystasz z Firefoksa, możesz go przetestować w tym miejscu (Chrome potrzebuje https):

'use strict'

let log = console.log.bind(console),
  id = val => document.getElementById(val),
  ul = id('ul'),
  gUMbtn = id('gUMbtn'),
  start = id('start'),
  stop = id('stop'),
  stream,
  recorder,
  counter = 1,
  chunks,
  media;


gUMbtn.onclick = e => {
  let mv = id('mediaVideo'),
    mediaOptions = {
      video: {
        tag: 'video',
        type: 'video/webm',
        ext: '.mp4',
        gUM: {
          video: true,
          audio: true
        }
      },
      audio: {
        tag: 'audio',
        type: 'audio/ogg',
        ext: '.ogg',
        gUM: {
          audio: true
        }
      }
    };
  media = mv.checked ? mediaOptions.video : mediaOptions.audio;
  navigator.mediaDevices.getUserMedia(media.gUM).then(_stream => {
    stream = _stream;
    id('gUMArea').style.display = 'none';
    id('btns').style.display = 'inherit';
    start.removeAttribute('disabled');
    recorder = new MediaRecorder(stream);
    recorder.ondataavailable = e => {
      chunks.push(e.data);
      if (recorder.state == 'inactive') makeLink();
    };
    log('got media successfully');
  }).catch(log);
}

start.onclick = e => {
  start.disabled = true;
  stop.removeAttribute('disabled');
  chunks = [];
  recorder.start();
}


stop.onclick = e => {
  stop.disabled = true;
  recorder.stop();
  start.removeAttribute('disabled');
}



function makeLink() {
  let blob = new Blob(chunks, {
      type: media.type
    }),
    url = URL.createObjectURL(blob),
    li = document.createElement('li'),
    mt = document.createElement(media.tag),
    hf = document.createElement('a');
  mt.controls = true;
  mt.src = url;
  hf.href = url;
  hf.download = `${counter++}${media.ext}`;
  hf.innerHTML = `donwload ${hf.download}`;
  li.appendChild(mt);
  li.appendChild(hf);
  ul.appendChild(li);
}
      button {
        margin: 10px 5px;
      }
      li {
        margin: 10px;
      }
      body {
        width: 90%;
        max-width: 960px;
        margin: 0px auto;
      }
      #btns {
        display: none;
      }
      h1 {
        margin-bottom: 100px;
      }
<link type="text/css" rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.6/css/bootstrap.min.css">
<h1> MediaRecorder API example</h1>

<p>For now it is supported only in Firefox(v25+) and Chrome(v47+)</p>
<div id='gUMArea'>
  <div>
    Record:
    <input type="radio" name="media" value="video" checked id='mediaVideo'>Video
    <input type="radio" name="media" value="audio">audio
  </div>
  <button class="btn btn-default" id='gUMbtn'>Request Stream</button>
</div>
<div id='btns'>
  <button class="btn btn-default" id='start'>Start</button>
  <button class="btn btn-default" id='stop'>Stop</button>
</div>
<div>
  <ul class="list-unstyled" id='ul'></ul>
</div>
<script src="https://code.jquery.com/jquery-2.2.0.min.js"></script>
<script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.6/js/bootstrap.min.js"></script>

mido
źródło
Chrome 49 jest pierwszym, który obsługuje MediaRecorder API bez flagi.
Octavian Naicu
7

tak, jak zrozumiałeś, MediaStreamRecorder nie jest obecnie zaimplementowany.

MediaStreamRecorder to API WebRTC do nagrywania strumieni getUserMedia (). Umożliwia aplikacjom internetowym tworzenie plików z sesji audio / wideo na żywo.

alternatywnie możesz to zrobić http://ericbidelman.tumblr.com/post/31486670538/creating-webm-video-from-getusermedia, ale brakuje dźwięku.

Konga Raju
źródło
1
Tak, możesz przechwycić plik audio, wysłać go na serwer i połączyć je tam, aby utworzyć prawdziwy plik wideo po stronie serwera. Ale to rozwiązanie może być bardzo powolne po stronie klienta w zależności od konfiguracji komputera, ponieważ musi tworzyć pliki obrazów za pomocą płótna ORAZ przechwytywać dźwięk, a wszystko to w pamięci RAM ... Przy okazji, zespół Firefox nad tym pracuje , więc miejmy nadzieję, że wkrótce go wydadzą.
Firas
4

Możesz używać RecordRTC razem , który jest oparty na RecordRTC.

Obsługuje nagrywanie wideo i audio razem w osobnych plikach. Będziesz potrzebował narzędzia takiego jak ffmpegpołączenie dwóch plików w jeden na serwerze.

Mifeng
źródło
2
To jest rozwiązanie oparte na przeglądarce, a nie po stronie serwera.
Brad
2

Web Call Server 4 może nagrywać dźwięk i wideo WebRTC do kontenera WebM. Nagrywanie odbywa się za pomocą kodeka Vorbis dla audio i kodeka VP8 dla wideo. Pierwotne kodeki WebRTC to Opus lub G.711 i VP8. Zatem nagrywanie po stronie serwera wymaga transkodowania Opus / G.711 do Vorbis po stronie serwera lub transkodowania VP8-H.264, jeśli konieczne jest użycie innego kontenera, tj. AVI.

Bob42
źródło
czy to sprawa komercyjna?
Stepan Yakovenko
0

Dla porządku, też nie mam wystarczającej wiedzy na ten temat,

Ale znalazłem to w centrum Git-

<!DOCTYPE html>
 <html>
<head>
  <title>XSockets.WebRTC Client example</title>
  <meta charset="utf-8" />


<style>
body {

  }
.localvideo {
position: absolute;
right: 10px;
top: 10px;
}

.localvideo video {
max-width: 240px;
width:100%;
margin-right:auto;
margin-left:auto;
border: 2px solid #333;

 }
 .remotevideos {
height:120px;
background:#dadada;
padding:10px; 
}

.remotevideos video{
max-height:120px;
float:left;
 }
</style>
</head>
<body>
<h1>XSockets.WebRTC Client example </h1>
<div class="localvideo">
    <video autoplay></video>
</div>

<h2>Remote videos</h2>
<div class="remotevideos">

</div>
<h2>Recordings  ( Click on your camera stream to start record)</h2>
<ul></ul>


<h2>Trace</h2>
<div id="immediate"></div>
<script src="XSockets.latest.js"></script>
<script src="adapter.js"></script>
<script src="bobBinder.js"></script>
<script src="xsocketWebRTC.js"></script>
<script>
    var $ = function (selector, el) {
        if (!el) el = document;
        return el.querySelector(selector);
    }
    var trace = function (what, obj) {
        var pre = document.createElement("pre");
        pre.textContent = JSON.stringify(what) + " - " + JSON.stringify(obj || "");
        $("#immediate").appendChild(pre);
    };
    var main = (function () {
        var broker;
        var rtc;
        trace("Ready");
        trace("Try connect the connectionBroker");
        var ws = new XSockets.WebSocket("wss://rtcplaygrouund.azurewebsites.net:443", ["connectionbroker"], {
            ctx: '23fbc61c-541a-4c0d-b46e-1a1f6473720a'
        });
        var onError = function (err) {
            trace("error", arguments);
        };
        var recordMediaStream = function (stream) {
            if ("MediaRecorder" in window === false) {
                trace("Recorder not started MediaRecorder not available in this browser. ");
                return;
            }
            var recorder = new XSockets.MediaRecorder(stream);
            recorder.start();
            trace("Recorder started.. ");
            recorder.oncompleted = function (blob, blobUrl) {
                trace("Recorder completed.. ");
                var li = document.createElement("li");
                var download = document.createElement("a");
                download.textContent = new Date();
                download.setAttribute("download", XSockets.Utils.randomString(8) + ".webm");
                download.setAttribute("href", blobUrl);
                li.appendChild(download);
                $("ul").appendChild(li);
            };
        };
        var addRemoteVideo = function (peerId, mediaStream) {
            var remoteVideo = document.createElement("video");
            remoteVideo.setAttribute("autoplay", "autoplay");
            remoteVideo.setAttribute("rel", peerId);
            attachMediaStream(remoteVideo, mediaStream);
            $(".remotevideos").appendChild(remoteVideo);
        };
        var onConnectionLost = function (remotePeer) {
            trace("onconnectionlost", arguments);
            var peerId = remotePeer.PeerId;
            var videoToRemove = $("video[rel='" + peerId + "']");
            $(".remotevideos").removeChild(videoToRemove);
        };
        var oncConnectionCreated = function () {
            console.log(arguments, rtc);
            trace("oncconnectioncreated", arguments);
        };
        var onGetUerMedia = function (stream) {
            trace("Successfully got some userMedia , hopefully a goat will appear..");
            rtc.connectToContext(); // connect to the current context?
        };
        var onRemoteStream = function (remotePeer) {
            addRemoteVideo(remotePeer.PeerId, remotePeer.stream);
            trace("Opps, we got a remote stream. lets see if its a goat..");
        };
        var onLocalStream = function (mediaStream) {
            trace("Got a localStream", mediaStream.id);
            attachMediaStream($(".localvideo video "), mediaStream);
            // if user click, video , call the recorder
            $(".localvideo video ").addEventListener("click", function () {
                recordMediaStream(rtc.getLocalStreams()[0]);
            });
        };
        var onContextCreated = function (ctx) {
            trace("RTC object created, and a context is created - ", ctx);
            rtc.getUserMedia(rtc.userMediaConstraints.hd(false), onGetUerMedia, onError);
        };
        var onOpen = function () {
            trace("Connected to the brokerController - 'connectionBroker'");
            rtc = new XSockets.WebRTC(this);
            rtc.onlocalstream = onLocalStream;
            rtc.oncontextcreated = onContextCreated;
            rtc.onconnectioncreated = oncConnectionCreated;
            rtc.onconnectionlost = onConnectionLost;
            rtc.onremotestream = onRemoteStream;
            rtc.onanswer = function (event) {
            };
            rtc.onoffer = function (event) {
            };
        };
        var onConnected = function () {
            trace("connection to the 'broker' server is established");
            trace("Try get the broker controller form server..");
            broker = ws.controller("connectionbroker");
            broker.onopen = onOpen;
        };
        ws.onconnected = onConnected;
    });
    document.addEventListener("DOMContentLoaded", main);
</script>

W linii numer 89 w moim przypadku kod OnrecordComplete faktycznie dołącza link do pliku rejestratora, jeśli klikniesz ten link, rozpocznie się pobieranie, możesz zapisać tę ścieżkę na serwerze jako plik.

Kod nagrywania wygląda mniej więcej tak

recorder.oncompleted = function (blob, blobUrl) {
                trace("Recorder completed.. ");
                var li = document.createElement("li");
                var download = document.createElement("a");
                download.textContent = new Date();
                download.setAttribute("download", XSockets.Utils.randomString(8) + ".webm");
                download.setAttribute("href", blobUrl);
                li.appendChild(download);
                $("ul").appendChild(li);
            };

BlobUrl przechowuje ścieżkę. Rozwiązałem swój problem z tym, mam nadzieję, że komuś to się przyda

unikalnyNt
źródło
-4

Z technicznego punktu widzenia można użyć FFMPEG na zapleczu do miksowania wideo i audio

EugeneB
źródło
7
tak, ale jak je tam dostarczysz?
Eddie Monge Jr