Transmisión en vivo co WebRTC en su aplicación Laravel
Introducción
Mi primer intento en WebRTC fue implementar una función de videollamada dentro de una aplicación Laravel. La implementación implicó realizar una llamada, mostrar una notificación de llamada entrante y la capacidad del receptor para aceptar la llamada. Escribí sobre eso aquí:

Agregar video chat a su aplicación Laravel
Kofi Mupati ・ 12 de noviembre de 2020 ・ 11 minutos de lectura
Uno de mis lectores preguntó si era posible crear una aplicación de transmisión en vivo con WebRTC en una aplicación Laravel. Acepté este desafío y, aunque WebRTC tiene limitaciones, se me ocurrió una implementación simple de transmisión en vivo.
Veremos mi implementación en este artículo.
Repositorio de proyectos finales:https://github.com/Mupati/laravel-video-chat Tenga en cuenta que este repositorio contiene código para algunos otros artículos técnicos.
Requisitos
Este tutorial asume que sabe cómo configurar un nuevo
Laravelproyecto conVueJsautenticación. Cree algunos usuarios después de configurar su proyecto. Debe estar familiarizado con el mecanismo de transmisión de Laravel y tener una idea clara de cómo funcionan los WebSockets. Puede usar este proyecto inicial que creé: Laravel 8 Vue Auth StarterConfigure una cuenta gratuita de pusher en pusher.com
Configure los detalles de su ICE SERVER (TURN SERVER). Este tutorial es una buena guía. CÓMO INSTALAR COTURN .
Configuración del proyecto
# Install needed packages
composer require pusher/pusher-PHP-server "~4.0"
npm install --save laravel-echo pusher-js simple-peer
Configuración de servidor
- Agregue rutas para páginas de transmisión en formato
routes/web.php. Las rutas se utilizarán para visitar la página de transmisión en vivo, iniciar una transmisión en vivo desde la cámara del dispositivo y generar un enlace de transmisión para que otros usuarios autenticados vean su transmisión en vivo.
Route::get('/streaming', 'App\Http\Controllers\WebrtcStreamingController@index');
Route::get('/streaming/{streamId}', 'App\Http\Controllers\WebrtcStreamingController@consumer');
Route::post('/stream-offer', 'App\Http\Controllers\WebrtcStreamingController@makeStreamOffer');
Route::post('/stream-answer', 'App\Http\Controllers\WebrtcStreamingController@makeStreamAnswer');
- Descomentar
BroadcastServiceProviderenconfig/app.php. Esto nos permite usar el sistema de transmisión de Laravel.
+ App\Providers\BroadcastServiceProvider::class
- //App\Providers\BroadcastServiceProvider::class
- Crear presencia dinámica y canal privado en rutas/canales.php.
Los usuarios autenticados se suscriben a ambos canales.
El canal de presencia se crea dinámicamente con un streamIdgenerado por la emisora. De esta manera, podemos detectar a todos los usuarios que se han unido a la transmisión en vivo.
La información de señalización se intercambia entre el emisor y el espectador a través del canal privado.
// Dynamic Presence Channel for Streaming
Broadcast::channel('streaming-channel.{streamId}', function ($user) {
return ['id' => $user->id, 'name' => $user->name];
});
// Signaling Offer and Answer Channels
Broadcast::channel('stream-signal-channel.{userId}', function ($user, $userId) {
return (int) $user->id === (int) $userId;
});
- Crear
StreamOfferyStreamAnswereventos. La información de señalización se transmite en elstream-signal-channel-{userId}canal privado que creamos al principio.
La emisora envía una oferta a un nuevo usuario que se une a la transmisión en vivo cuando emitimos el StreamOfferevento y el espectador responde con una respuesta utilizando el StreamAnswerevento.
php artisan make:event StreamOffer
php artisan make:event StreamAnswer
- Agregue el siguiente código a
app/Events/StreamOffer.php.
<?php
namespace App\Events;
use Illuminate\Broadcasting\InteractsWithSockets;
use Illuminate\Broadcasting\PrivateChannel;
use Illuminate\Contracts\Broadcasting\ShouldBroadcast;
use Illuminate\Foundation\Events\Dispatchable;
use Illuminate\Queue\SerializesModels;
class StreamOffer implements ShouldBroadcast
{
use Dispatchable, InteractsWithSockets, SerializesModels;
public $data;
/**
* Create a new event instance.
*
* @return void
*/
public function __construct($data)
{
$this->data = $data;
}
/**
* Get the channels the event should broadcast on.
*
* @return \Illuminate\Broadcasting\Channel|array
*/
public function broadcastOn()
{
// stream offer can broadcast on a private channel
return new PrivateChannel('stream-signal-channel.' . $this->data['receiver']['id']);
}
}
- Agregue el siguiente código a
app/Events/StreamAnswer.php.
<?php
namespace App\Events;
use Illuminate\Broadcasting\InteractsWithSockets;
use Illuminate\Broadcasting\PrivateChannel;
use Illuminate\Contracts\Broadcasting\ShouldBroadcast;
use Illuminate\Foundation\Events\Dispatchable;
use Illuminate\Queue\SerializesModels;
class StreamAnswer implements ShouldBroadcast
{
use Dispatchable, InteractsWithSockets, SerializesModels;
public $data;
/**
* Create a new event instance.
*
* @return void
*/
public function __construct($data)
{
$this->data = $data;
}
/**
* Get the channels the event should broadcast on.
*
* @return \Illuminate\Broadcasting\Channel|array
*/
public function broadcastOn()
{
return new PrivateChannel('stream-signal-channel.' . $this->data['broadcaster']);
}
}
- Crear
WebrtcStreamingControllerpara manejar la transmisión, la visualización y la señalización de la transmisión en vivo.
php artisan make:controller WebrtcStreamingController
- Agregue lo siguiente a la
WebrtcStreamingController
<?php
namespace App\Http\Controllers;
use App\Events\StreamAnswer;
use App\Events\StreamOffer;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
class WebrtcStreamingController extends Controller
{
public function index()
{
return view('video-broadcast', ['type' => 'broadcaster', 'id' => Auth::id()]);
}
public function consumer(Request $request, $streamId)
{
return view('video-broadcast', ['type' => 'consumer', 'streamId' => $streamId, 'id' => Auth::id()]);
}
public function makeStreamOffer(Request $request)
{
$data['broadcaster'] = $request->broadcaster;
$data['receiver'] = $request->receiver;
$data['offer'] = $request->offer;
event(new StreamOffer($data));
}
public function makeStreamAnswer(Request $request)
{
$data['broadcaster'] = $request->broadcaster;
$data['answer'] = $request->answer;
event(new StreamAnswer($data));
}
}
Métodos en WebrtcStreamingController
Exploremos qué están haciendo los métodos en el controlador.
index: Esto devuelve la vista para el emisor. Pasamos un 'tipo': emisor y el ID del usuario a la vista para ayudar a identificar quién es el usuario.consumer: Devuelve la vista para un nuevo usuario que quiere unirse a la transmisión en vivo. Pasamos un 'tipo': consumidor, el 'streamId' que extraemos del enlace de transmisión y la identificación del usuario.makeStreamOffer: Emite una señal de oferta enviada por el emisor a un usuario específico que se acaba de incorporar. Se envían los siguientes datos:broadcaster: El ID de usuario del que inició la transmisión en vivo, es decir, el emisorreceiver: El ID del usuario al que se envía la oferta de señalización.offer: Esta es la oferta WebRTC de la emisora.
makeStreamAnswer: Envía una señal de respuesta a la emisora para establecer completamente la conexión entre pares.broadcaster: El ID de usuario del que inició la transmisión en vivo, es decir, el emisor.answer: Esta es la respuesta WebRTC del espectador, enviada después de recibir una oferta de la emisora.
Configuración de interfaz
- Cree una instancia
Laravel Echoy descomentePusherelresources/js/bootstrap.jssiguiente bloque de código.
+ import Echo from 'laravel-echo';
+ window.Pusher = require('pusher-js');
+ window.Echo = new Echo({
+ broadcaster: 'pusher',
+ key: process.env.MIX_PUSHER_APP_KEY,
+ cluster: process.env.MIX_PUSHER_APP_CLUSTER,
+ forceTLS: true
+ });
- import Echo from 'laravel-echo';
- window.Pusher = require('pusher-js');
- window.Echo = new Echo({
- broadcaster: 'pusher',
- key: process.env.MIX_PUSHER_APP_KEY,
- cluster: process.env.MIX_PUSHER_APP_CLUSTER,
- forceTLS: true
-});
- Crear
resources/js/helpers.js_ Agregue unagetPermissionsfunción para ayudar con el acceso de permisos para el micrófono y la cámara. Este método maneja el permiso de video y audio que requieren los navegadores para realizar las videollamadas. Espera a que el usuario acepte los permisos antes de que podamos continuar con la videollamada. Permitimos audio y video. Lea más en el sitio web de MDN .
export const getPermissions = () => {
// Older browsers might not implement mediaDevices at all, so we set an empty object first
if (navigator.mediaDevices === undefined) {
navigator.mediaDevices = {};
}
// Some browsers partially implement media devices. We can't just assign an object
// with getUserMedia as it would overwrite existing properties.
// Here, we will just add the getUserMedia property if it's missing.
if (navigator.mediaDevices.getUserMedia === undefined) {
navigator.mediaDevices.getUserMedia = function(constraints) {
// First get ahold of the legacy getUserMedia, if present
const getUserMedia =
navigator.webkitGetUserMedia || navigator.mozGetUserMedia;
// Some browsers just don't implement it - return a rejected promise with an error
// to keep a consistent interface
if (!getUserMedia) {
return Promise.reject(
new Error("getUserMedia is not implemented in this browser")
);
}
// Otherwise, wrap the call to the old navigator.getUserMedia with a Promise
return new Promise((resolve, reject) => {
getUserMedia.call(navigator, constraints, resolve, reject);
});
};
}
navigator.mediaDevices.getUserMedia =
navigator.mediaDevices.getUserMedia ||
navigator.webkitGetUserMedia ||
navigator.mozGetUserMedia;
return new Promise((resolve, reject) => {
navigator.mediaDevices
.getUserMedia({ video: true, audio: true })
.then(stream => {
resolve(stream);
})
.catch(err => {
reject(err);
// throw new Error(`Unable to fetch stream ${err}`);
});
});
};
Cree un componente para Broadcaster denominado Broadcaster.vue en formato
resources/js/components/Broadcaster.vue.Cree un componente para el Visor denominado Viewer.vue en formato
resources/js/components/Viewer.vue.
Explicación de los componentes Emisor y Visor.
El siguiente video explica la lógica de la llamada en el lado del cliente desde la perspectiva tanto del emisor como de los espectadores.
- Registre los componentes
Broadcaster.vuey enViewer.vueresources/js/app.js
// Streaming Components
Vue.component("broadcaster", require("./components/Broadcaster.vue").default);
Vue.component("viewer", require("./components/Viewer.vue").default);
Cree la vista de transmisión de video en
resources/views/video-broadcast.blade.phpActualizar variables de entorno. Inserte sus claves API de Pusher
APP_ENV=
BROADCAST_DRIVER=pusher
PUSHER_APP_ID=
PUSHER_APP_KEY=
PUSHER_APP_SECRET=
PUSHER_APP_CLUSTER=
TURN_SERVER_URL=
TURN_SERVER_USERNAME=
TURN_SERVER_CREDENTIAL=
Demostración de transmisión en vivo
Pensamientos finales
La lógica de esta aplicación de transmisión en vivo se puede comparar con una videollamada grupal en la que solo se ve la transmisión de una persona.
La transmisión de la emisora se representa en el navegador de los espectadores, pero la emisora no recibe nada de los espectadores después de intercambiar la información de señalización que se requiere en WebRTC.
Esto parece una topología en estrella y existe una limitación sobre la cantidad de pares que se pueden conectar a un solo usuario.
Quiero explorar la opción de convertir a algunos de los espectadores en locutores después de que el par del locutor inicial se haya conectado a unos 4 usuarios.
El objetivo es retransmitir la transmisión que recibieron de la emisora original.
¿Es posible? no puedo decir Este será un desafío interesante para explorar.
¡¡¡Manténganse al tanto!!!.
Comentarios
Publicar un comentario