Add live stream events

This commit is contained in:
Daniel Supernault 2022-06-26 16:06:08 -06:00
parent dba78e0826
commit aa498af073
No known key found for this signature in database
GPG key ID: 0DEF1C662C9033F7
11 changed files with 416 additions and 31 deletions

View file

@ -0,0 +1,51 @@
<?php
namespace App\Events\LiveStream;
use Illuminate\Broadcasting\Channel;
use Illuminate\Broadcasting\InteractsWithSockets;
use Illuminate\Broadcasting\PresenceChannel;
use Illuminate\Broadcasting\PrivateChannel;
use Illuminate\Contracts\Broadcasting\ShouldBroadcast;
use Illuminate\Foundation\Events\Dispatchable;
use Illuminate\Queue\SerializesModels;
use App\Models\LiveStream;
class BanUser implements ShouldBroadcast
{
use Dispatchable, InteractsWithSockets, SerializesModels;
public $livestream;
public $profileId;
/**
* Create a new event instance.
*
* @return void
*/
public function __construct(LiveStream $livestream, $profileId)
{
$this->livestream = $livestream;
$this->profileId = $profileId;
}
/**
* Get the channels the event should broadcast on.
*
* @return \Illuminate\Broadcasting\Channel|array
*/
public function broadcastOn()
{
return new PrivateChannel('live.chat.' . $this->livestream->profile_id);
}
public function broadcastAs()
{
return 'chat.ban-user';
}
public function broadcastWith()
{
return ['id' => $this->profileId];
}
}

View file

@ -0,0 +1,51 @@
<?php
namespace App\Events\LiveStream;
use Illuminate\Broadcasting\Channel;
use Illuminate\Broadcasting\InteractsWithSockets;
use Illuminate\Broadcasting\PresenceChannel;
use Illuminate\Broadcasting\PrivateChannel;
use Illuminate\Contracts\Broadcasting\ShouldBroadcast;
use Illuminate\Foundation\Events\Dispatchable;
use Illuminate\Queue\SerializesModels;
use App\Models\LiveStream;
class DeleteChatComment implements ShouldBroadcast
{
use Dispatchable, InteractsWithSockets, SerializesModels;
public $livestream;
public $chatmsg;
/**
* Create a new event instance.
*
* @return void
*/
public function __construct(LiveStream $livestream, $chatmsg)
{
$this->livestream = $livestream;
$this->chatmsg = $chatmsg;
}
/**
* Get the channels the event should broadcast on.
*
* @return \Illuminate\Broadcasting\Channel|array
*/
public function broadcastOn()
{
return new PrivateChannel('live.chat.' . $this->livestream->profile_id);
}
public function broadcastAs()
{
return 'chat.delete-message';
}
public function broadcastWith()
{
return ['id' => $this->chatmsg['id']];
}
}

View file

@ -0,0 +1,51 @@
<?php
namespace App\Events\LiveStream;
use Illuminate\Broadcasting\Channel;
use Illuminate\Broadcasting\InteractsWithSockets;
use Illuminate\Broadcasting\PresenceChannel;
use Illuminate\Broadcasting\PrivateChannel;
use Illuminate\Contracts\Broadcasting\ShouldBroadcast;
use Illuminate\Foundation\Events\Dispatchable;
use Illuminate\Queue\SerializesModels;
use App\Models\LiveStream;
class NewChatComment implements ShouldBroadcast
{
use Dispatchable, InteractsWithSockets, SerializesModels;
public $livestream;
public $chatmsg;
/**
* Create a new event instance.
*
* @return void
*/
public function __construct(LiveStream $livestream, $chatmsg)
{
$this->livestream = $livestream;
$this->chatmsg = $chatmsg;
}
/**
* Get the channels the event should broadcast on.
*
* @return \Illuminate\Broadcasting\Channel|array
*/
public function broadcastOn()
{
return new PrivateChannel('live.chat.' . $this->livestream->profile_id);
}
public function broadcastAs()
{
return 'chat.new-message';
}
public function broadcastWith()
{
return ['msg' => $this->chatmsg];
}
}

View file

@ -0,0 +1,51 @@
<?php
namespace App\Events\LiveStream;
use Illuminate\Broadcasting\Channel;
use Illuminate\Broadcasting\InteractsWithSockets;
use Illuminate\Broadcasting\PresenceChannel;
use Illuminate\Broadcasting\PrivateChannel;
use Illuminate\Contracts\Broadcasting\ShouldBroadcast;
use Illuminate\Foundation\Events\Dispatchable;
use Illuminate\Queue\SerializesModels;
use App\Models\LiveStream;
class PinChatMessage implements ShouldBroadcast
{
use Dispatchable, InteractsWithSockets, SerializesModels;
public $livestream;
public $chatmsg;
/**
* Create a new event instance.
*
* @return void
*/
public function __construct(LiveStream $livestream, $chatmsg)
{
$this->livestream = $livestream;
$this->chatmsg = $chatmsg;
}
/**
* Get the channels the event should broadcast on.
*
* @return \Illuminate\Broadcasting\Channel|array
*/
public function broadcastOn()
{
return new PrivateChannel('live.chat.' . $this->livestream->profile_id);
}
public function broadcastAs()
{
return 'chat.pin-message';
}
public function broadcastWith()
{
return $this->chatmsg;
}
}

View file

@ -0,0 +1,51 @@
<?php
namespace App\Events\LiveStream;
use Illuminate\Broadcasting\Channel;
use Illuminate\Broadcasting\InteractsWithSockets;
use Illuminate\Broadcasting\PresenceChannel;
use Illuminate\Broadcasting\PrivateChannel;
use Illuminate\Contracts\Broadcasting\ShouldBroadcast;
use Illuminate\Foundation\Events\Dispatchable;
use Illuminate\Queue\SerializesModels;
use App\Models\LiveStream;
class UnpinChatMessage implements ShouldBroadcast
{
use Dispatchable, InteractsWithSockets, SerializesModels;
public $livestream;
public $chatmsg;
/**
* Create a new event instance.
*
* @return void
*/
public function __construct(LiveStream $livestream, $chatmsg)
{
$this->livestream = $livestream;
$this->chatmsg = $chatmsg;
}
/**
* Get the channels the event should broadcast on.
*
* @return \Illuminate\Broadcasting\Channel|array
*/
public function broadcastOn()
{
return new PrivateChannel('live.chat.' . $this->livestream->profile_id);
}
public function broadcastAs()
{
return 'chat.unpin-message';
}
public function broadcastWith()
{
return $this->chatmsg;
}
}

View file

@ -101,6 +101,16 @@ class ApiV1Controller extends Controller
return response()->json($res, $code, $headers, JSON_UNESCAPED_SLASHES);
}
public function getWebsocketConfig()
{
return config('broadcasting.default') === 'pusher' ? [
'host' => config('broadcasting.connections.pusher.options.host'),
'port' => config('broadcasting.connections.pusher.options.port'),
'key' => config('broadcasting.connections.pusher.key'),
'cluster' => config('broadcasting.connections.pusher.options.cluster')
] : [];
}
public function getApp(Request $request)
{
if(!$request->user()) {

View file

@ -9,6 +9,12 @@ use Illuminate\Support\Facades\Storage;
use App\Services\AccountService;
use App\Services\FollowerService;
use App\Services\LiveStreamService;
use App\User;
use App\Events\LiveStream\NewChatComment;
use App\Events\LiveStream\DeleteChatComment;
use App\Events\LiveStream\BanUser;
use App\Events\LiveStream\PinChatMessage;
use App\Events\LiveStream\UnpinChatMessage;
class LiveStreamController extends Controller
{
@ -63,32 +69,22 @@ class LiveStreamController extends Controller
abort_if(!config('livestreaming.enabled'), 400);
abort_if(!$request->user(), 403);
$stream = LiveStream::whereProfileId($request->input('profile_id'))->first();
$stream = LiveStream::whereProfileId($request->input('profile_id'))
->whereNotNull('live_at')
->orderByDesc('live_at')
->first();
if(!$stream) {
return [];
}
$res = [];
$owner = $stream->profile_id == $request->user()->profile_id;
$owner = $request->user() ? $stream->profile_id == $request->user()->profile_id : false;
if($stream->visibility === 'private') {
abort_if(!$owner && !FollowerService::follows($request->user()->profile_id, $stream->profile_id), 403, 'LSE:011');
}
if($owner) {
$res['stream_key'] = $stream->stream_key;
$res['stream_id'] = $stream->stream_id;
$res['stream_url'] = $stream->getStreamKeyUrl();
}
if($stream->live_at == null) {
$res['hls_url'] = null;
$res['name'] = $stream->name;
$res['description'] = $stream->description;
return $res;
}
$res = [
'hls_url' => $stream->getHlsUrl(),
'name' => $stream->name,
@ -98,6 +94,47 @@ class LiveStreamController extends Controller
return response()->json($res, 200, [], JSON_UNESCAPED_SLASHES);
}
public function getUserStreamAsGuest(Request $request)
{
abort_if(!config('livestreaming.enabled'), 400);
$stream = LiveStream::whereProfileId($request->input('profile_id'))
->whereVisibility('public')
->whereNotNull('live_at')
->orderByDesc('live_at')
->first();
if(!$stream) {
return [];
}
$res = [];
$res = [
'hls_url' => $stream->getHlsUrl(),
'name' => $stream->name,
'description' => $stream->description
];
return response()->json($res, 200, [], JSON_UNESCAPED_SLASHES);
}
public function showProfilePlayer(Request $request, $username)
{
abort_if(!config('livestreaming.enabled'), 400);
$user = User::whereUsername($username)->firstOrFail();
$id = (string) $user->profile_id;
$stream = LiveStream::whereProfileId($id)
->whereNotNull('live_at')
->first();
abort_if(!$request->user() && $stream->visibility !== 'public', 404);
return view('live.player', compact('id'));
}
public function deleteStream(Request $request)
{
abort_if(!config('livestreaming.enabled'), 400);
@ -118,7 +155,7 @@ class LiveStreamController extends Controller
abort_if(!config('livestreaming.enabled'), 400);
abort_if(!$request->user(), 403);
return LiveStream::whereVisibility('local')->whereNotNull('live_at')->get()->map(function($stream) {
return LiveStream::whereIn('visibility', ['local', 'public'])->whereNotNull('live_at')->get()->map(function($stream) {
return [
'account' => AccountService::get($stream->profile_id),
'stream_id' => $stream->stream_id
@ -162,22 +199,30 @@ class LiveStreamController extends Controller
'message' => 'required|max:140'
]);
$stream = LiveStream::whereProfileId($request->input('profile_id'))->firstOrFail();
$stream = LiveStream::whereProfileId($request->input('profile_id'))
->whereNotNull('live_at')
->firstOrFail();
$owner = $stream->profile_id == $request->user()->profile_id;
if($stream->visibility === 'private') {
abort_if(!$owner && !FollowerService::follows($request->user()->profile_id, $stream->profile_id), 403, 'LSE:022');
abort_if(!$owner && !FollowerService::follows($request->user()->profile_id, $stream->profile_id), 403);
}
$user = AccountService::get($request->user()->profile_id);
abort_if(!$user, 422);
$res = [
'id' => (string) Str::uuid(),
'pid' => (string) $request->user()->profile_id,
'username' => $request->user()->username,
'avatar' => $user['avatar'],
'username' => $user['username'],
'text' => $request->input('message'),
'ts' => now()->timestamp
];
LiveStreamService::addComment($stream->profile_id, json_encode($res, JSON_UNESCAPED_SLASHES));
NewChatComment::dispatch($stream, $res);
return $res;
}
@ -209,14 +254,79 @@ class LiveStreamController extends Controller
'message' => 'required'
]);
abort_if($request->user()->profile_id != $request->input('profile_id'), 403);
$uid = $request->user()->profile_id;
$pid = $request->input('profile_id');
$msg = $request->input('message');
$admin = $uid == $request->input('profile_id');
$owner = $uid == $msg['pid'];
abort_if(!$admin && !$owner, 403);
$stream = LiveStream::whereProfileId($request->user()->profile_id)->firstOrFail();
$stream = LiveStream::whereProfileId($pid)->firstOrFail();
$payload = $request->input('message');
broadcast(new DeleteChatComment($stream, $payload))->toOthers();
$payload = json_encode($payload, JSON_UNESCAPED_SLASHES);
LiveStreamService::deleteComment($stream->profile_id, $payload);
return;
}
public function banChatUser(Request $request)
{
abort_if(!config('livestreaming.enabled'), 400);
abort_if(!$request->user(), 403);
$this->validate($request, [
'profile_id' => 'required|exists:profiles,id',
]);
abort_if($request->user()->profile_id == $request->input('profile_id'), 403);
$stream = LiveStream::whereProfileId($request->user()->profile_id)->firstOrFail();
$pid = $request->input('profile_id');
BanUser::dispatch($stream, $pid);
return;
}
public function pinChatComment(Request $request)
{
abort_if(!config('livestreaming.enabled'), 400);
abort_if(!$request->user(), 403);
$this->validate($request, [
'profile_id' => 'required|exists:profiles,id',
'message' => 'required'
]);
$uid = $request->user()->profile_id;
$pid = $request->input('profile_id');
$msg = $request->input('message');
abort_if($uid != $pid, 403);
$stream = LiveStream::whereProfileId($request->user()->profile_id)->firstOrFail();
PinChatMessage::dispatch($stream, $msg);
return;
}
public function unpinChatComment(Request $request)
{
abort_if(!config('livestreaming.enabled'), 400);
abort_if(!$request->user(), 403);
$this->validate($request, [
'profile_id' => 'required|exists:profiles,id',
'message' => 'required'
]);
$uid = $request->user()->profile_id;
$pid = $request->input('profile_id');
$msg = $request->input('message');
abort_if($uid != $pid, 403);
$stream = LiveStream::whereProfileId($request->user()->profile_id)->firstOrFail();
UnpinChatMessage::dispatch($stream, $msg);
return;
}

View file

@ -154,10 +154,11 @@ return [
*/
App\Providers\AppServiceProvider::class,
App\Providers\AuthServiceProvider::class,
// App\Providers\BroadcastServiceProvider::class,
App\Providers\BroadcastServiceProvider::class,
App\Providers\HorizonServiceProvider::class,
App\Providers\EventServiceProvider::class,
App\Providers\RouteServiceProvider::class,
App\Providers\TelescopeServiceProvider::class,
App\Providers\PassportServiceProvider::class,
],

View file

@ -37,14 +37,10 @@ return [
'app_id' => env('PUSHER_APP_ID'),
'options' => [
'cluster' => env('PUSHER_APP_CLUSTER'),
'encrypted' => true,
'host' => env('APP_DOMAIN'),
'port' => 6001,
'scheme' => 'https',
'curl_options' => [
CURLOPT_SSL_VERIFYHOST => 0,
CURLOPT_SSL_VERIFYPEER => 0,
]
'encrypted' => env('PUSHER_APP_ENCRYPTED', false),
'host' => env('PUSHER_HOST', env('APP_DOMAIN')),
'port' => env('PUSHER_PORT', 443),
'scheme' => env('PUSHER_SCHEME', 'https')
],
],

View file

@ -94,6 +94,7 @@ Route::group(['prefix' => 'api'], function() use($middleware) {
Route::group(['prefix' => 'v2'], function() use($middleware) {
Route::get('search', 'Api\ApiV1Controller@searchV2')->middleware($middleware);
Route::post('media', 'Api\ApiV1Controller@mediaUploadV2')->middleware($middleware);
Route::get('streaming/config', 'Api\ApiV1Controller@getWebsocketConfig');
});
Route::group(['prefix' => 'live'], function() use($middleware) {
@ -101,10 +102,14 @@ Route::group(['prefix' => 'api'], function() use($middleware) {
Route::post('stream/edit', 'LiveStreamController@editStream')->middleware($middleware);
Route::get('active/list', 'LiveStreamController@getActiveStreams')->middleware($middleware);
Route::get('accounts/stream', 'LiveStreamController@getUserStream')->middleware($middleware);
Route::get('accounts/stream/guest', 'LiveStreamController@getUserStreamAsGuest');
Route::delete('accounts/stream', 'LiveStreamController@deleteStream')->middleware($middleware);
Route::get('chat/latest', 'LiveStreamController@getLatestChat')->middleware($middleware);
Route::post('chat/message', 'LiveStreamController@addChatComment')->middleware($middleware);
Route::post('chat/delete', 'LiveStreamController@deleteChatComment')->middleware($middleware);
Route::post('chat/ban-user', 'LiveStreamController@banChatUser')->middleware($middleware);
Route::post('chat/pin', 'LiveStreamController@pinChatComment')->middleware($middleware);
Route::post('chat/unpin', 'LiveStreamController@unpinChatComment')->middleware($middleware);
Route::get('config', 'LiveStreamController@getConfig')->middleware($middleware);
Route::post('broadcast/publish', 'LiveStreamController@clientBroadcastPublish');
Route::post('broadcast/finish', 'LiveStreamController@clientBroadcastFinish');

View file

@ -14,3 +14,11 @@
Broadcast::channel('App.User.{id}', function ($user, $id) {
return (int) $user->id === (int) $id;
});
Broadcast::channel('live.chat.{id}', function ($user, $id) {
return true;
}, ['guards' => ['web', 'api']]);
Broadcast::channel('live.presence.{id}', function ($user, $id) {
return [ $user->profile_id ];
}, ['guards' => ['web', 'api']]);