Skip to content

Commit 5f165ad

Browse files
authored
Merge pull request #53 from mintopia/feature/youtube
Prototype Youtube Player
2 parents 3d5c8a0 + 9e03e61 commit 5f165ad

File tree

11 files changed

+264
-1
lines changed

11 files changed

+264
-1
lines changed
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
<?php
2+
3+
namespace App\Events\Party;
4+
5+
use App\Models\Party;
6+
use Illuminate\Broadcasting\Channel;
7+
use Illuminate\Broadcasting\InteractsWithSockets;
8+
use Illuminate\Contracts\Broadcasting\ShouldBroadcast;
9+
use Illuminate\Foundation\Events\Dispatchable;
10+
use Illuminate\Queue\SerializesModels;
11+
12+
class PlayYouTubeVideoEvent implements ShouldBroadcast
13+
{
14+
use Dispatchable;
15+
use InteractsWithSockets;
16+
use SerializesModels;
17+
18+
/**
19+
* Create a new event instance.
20+
*/
21+
public function __construct(protected Party $party, protected string $videoId)
22+
{
23+
}
24+
25+
public function broadcastWith(): array
26+
{
27+
return [
28+
'video' => $this->videoId,
29+
];
30+
}
31+
32+
/**
33+
* Get the channels the event should broadcast on.
34+
*
35+
* @return array<int, \Illuminate\Broadcasting\Channel>
36+
*/
37+
public function broadcastOn()
38+
{
39+
return [
40+
new Channel("party.{$this->party->code}"),
41+
];
42+
}
43+
}

app/Http/Controllers/PartyController.php

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,11 @@
22

33
namespace App\Http\Controllers;
44

5+
use App\Http\Requests\PartyPlayYouTubeRequest;
56
use App\Http\Requests\PartyRequest;
67
use App\Http\Requests\SearchRequest;
78
use App\Http\Resources\V1\UpcomingSongResource;
9+
use App\Jobs\PartyPlayYouTubeVideo;
810
use App\Models\Party;
911
use App\Models\UpcomingSong;
1012
use App\Services\SpotifySearchService;
@@ -84,6 +86,42 @@ public function tv(Party $party)
8486
]);
8587
}
8688

89+
public function youtube(Request $request, Party $party)
90+
{
91+
return view('parties.youtube', [
92+
'party' => $party,
93+
'canManage' => $party->canBeManagedBy($request->user()),
94+
]);
95+
}
96+
97+
public function youtube_play(PartyPlayYouTubeRequest $request, Party $party)
98+
{
99+
$videoId = null;
100+
$url = parse_url($request->input('video'));
101+
if ($url['host'] === 'youtu.be') {
102+
// https://youtu.be/Lp__P8VBR5o?si=teYIzjNQoHuD7SEU
103+
$videoId = substr($url['path'] ?? '', 1);
104+
} else {
105+
$query = [];
106+
parse_str($url['query'] ?? '', $query);
107+
$videoId = $query['v'] ?? null;
108+
}
109+
if ($videoId === null || !$videoId) {
110+
return response()->redirectToRoute('parties.youtube', ['party' => $party->code])
111+
->with('failureMessage', 'Unable to identify video');
112+
}
113+
PartyPlayYouTubeVideo::dispatch($party, $videoId)->afterResponse();
114+
return response()->redirectToRoute('parties.youtube', ['party' => $party->code])
115+
->with('successMessage', 'Video requested');
116+
}
117+
118+
public function ytplayer(Party $party)
119+
{
120+
return view('parties.ytplayer', [
121+
'party' => $party,
122+
]);
123+
}
124+
87125
public function search(SearchRequest $request, Party $party)
88126
{
89127
$member = $party->getMember($request->user());
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
<?php
2+
3+
namespace App\Http\Requests;
4+
5+
use Illuminate\Foundation\Http\FormRequest;
6+
7+
class PartyPlayYouTubeRequest extends FormRequest
8+
{
9+
/**
10+
* Determine if the user is authorized to make this request.
11+
*/
12+
public function authorize(): bool
13+
{
14+
return true;
15+
}
16+
17+
/**
18+
* Get the validation rules that apply to the request.
19+
*
20+
* @return array<string, \Illuminate\Contracts\Validation\ValidationRule|array<mixed>|string>
21+
*/
22+
public function rules(): array
23+
{
24+
return [
25+
'video' => 'required|url',
26+
];
27+
}
28+
}

app/Jobs/PartyPlayYouTubeVideo.php

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
<?php
2+
3+
namespace App\Jobs;
4+
5+
use App\Events\Party\PlayYouTubeVideoEvent;
6+
use App\Models\Party;
7+
use Illuminate\Contracts\Queue\ShouldQueue;
8+
use Illuminate\Foundation\Bus\Dispatchable;
9+
use Illuminate\Queue\InteractsWithQueue;
10+
use Illuminate\Queue\SerializesModels;
11+
use Illuminate\Bus\Queueable;
12+
13+
class PartyPlayYouTubeVideo implements ShouldQueue
14+
{
15+
use Dispatchable;
16+
use InteractsWithQueue;
17+
use Queueable;
18+
use SerializesModels;
19+
20+
/**
21+
* Create a new job instance.
22+
*/
23+
public function __construct(protected Party $party, protected string $videoId)
24+
{
25+
$this->onQueue('partyupdates');
26+
}
27+
28+
/**
29+
* Execute the job.
30+
*/
31+
public function handle(): void
32+
{
33+
PlayYouTubeVideoEvent::dispatch($this->party, $this->videoId)->dispatch();
34+
}
35+
}

app/Providers/AppServiceProvider.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ public function boot(): void
2929
return "<?php echo App\Models\Setting::fetch($expression, $default); ?>";
3030
});
3131

32-
view()->composer(['layouts.app', 'layouts.login', 'parties.tv'], function ($view) {
32+
view()->composer(['layouts.app', 'layouts.login', 'parties.tv', 'parties.ytplayer'], function ($view) {
3333
$currentTheme = Theme::whereActive(true)->first();
3434
$darkMode = false;
3535
if ($currentTheme) {

resources/js/app.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import Next from './components/Next.vue'
88
import Upcoming from './components/Upcoming.vue'
99
import SearchResult from './components/SearchResult.vue'
1010
import TvPlayer from './components/TvPlayer.vue'
11+
import YouTubePlayer from './components/YouTubePlayer.vue'
1112

1213
import.meta.glob([
1314
'../img/**',
@@ -21,5 +22,6 @@ app.component('next', Next)
2122
app.component('upcoming', Upcoming)
2223
app.component('search-result', SearchResult)
2324
app.component('tv-player', TvPlayer)
25+
app.component('youtube-player', YouTubePlayer)
2426

2527
app.mount('#app')
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
<template>
2+
<div class="player p-0 vh-100 w-100 bg-black text-white">
3+
<iframe class="vh-100 w-100" v-bind:src="youtubeUrl" />
4+
</div>
5+
</template>
6+
<style>
7+
8+
</style>
9+
<script>
10+
export default {
11+
props: [
12+
'code',
13+
],
14+
data() {
15+
return {
16+
'youtubeUrl': null,
17+
}
18+
},
19+
20+
methods: {
21+
playVideo(videoId) {
22+
console.log(`Play ${videoId}`);
23+
const cb = Date.now();
24+
this.youtubeUrl = `https://www.youtube.com/embed/${videoId}?autoplay=1&cb=${cb}`;
25+
}
26+
},
27+
28+
mounted() {
29+
let channel = `party.${this.code}`;
30+
console.log('Mounted');
31+
window.Echo.channel(channel).listen('Party\\PlayYouTubeVideoEvent', (payload) => {
32+
this.playVideo(payload.video);
33+
});
34+
},
35+
36+
created() {
37+
},
38+
}
39+
</script>

resources/views/layouts/app.blade.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,7 @@
8181
<a class="dropdown-item" href="{{ route('parties.songs.index', $member->party->code) }}">Songs</a>
8282
<a class="dropdown-item" href="{{ route('parties.users.index', $member->party->code) }}">Users</a>
8383
<a class="dropdown-item" href="{{ route('parties.tv', $member->party->code) }}">TV Mode</a>
84+
<a class="dropdown-item" href="{{ route('parties.youtube', $member->party->code) }}">YouTube Player</a>
8485
<a class="dropdown-item" href="{{ route('parties.player', $member->party->code) }}">Web Player</a>
8586
<a class="dropdown-item" href="{{ route('parties.edit', $member->party->code) }}">Settings</a>
8687
@endif
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
@extends('layouts.app', [
2+
'activenav' => "party:{$party->code}"
3+
])
4+
5+
@section('breadcrumbs')
6+
<li class="breadcrumb-item"><a href="{{ route('home') }}">Home</a></li>
7+
<li class="breadcrumb-item"><a href="{{ route('parties.show', $party->code) }}">{{ $party->name }}</a>
8+
<li class="breadcrumb-item active"><a href="{{ route('parties.youtube', $party->code) }}">Play YouTube Video</a>
9+
@endsection
10+
@push('precontainer')
11+
@include('parties._player')
12+
@endpush
13+
@section('content')
14+
<div class="col-xl-8 offset-xl-2 col-lg-10 offset-lg-1">
15+
<div class="page-header mt-0">
16+
<h1>YouTube Player</h1>
17+
</div>
18+
<form action="{{ route('parties.youtube', $party->code) }}" method="post">
19+
{{ csrf_field() }}
20+
<div class="d-flex">
21+
<div class="flex-grow-1">
22+
<div class="input-icon">
23+
<input type="text" class="form-control" name="video" id="video" placeholder="YouTube URL">
24+
<span class="input-icon-addon">
25+
<i class="icon ti ti-player-play"></i>
26+
</span>
27+
</div>
28+
</div>
29+
<div class="flex-column ps-2">
30+
<button type="submit" class="btn btn-primary">Play</button>
31+
</div>
32+
</div>
33+
</form>
34+
<pre class="mt-4 mb-4">{{ route('parties.ytplayer', ['party' => $party->code]) }}
35+
</div>
36+
@endsection
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
<!doctype html>
2+
<html lang="en">
3+
<head>
4+
<meta charset="utf-8"/>
5+
<meta name="viewport" content="width=device-width, initial-scale=1, viewport-fit=cover"/>
6+
<meta http-equiv="X-UA-Compatible" content="ie=edge"/>
7+
8+
<script>
9+
window.pusherConfig = {
10+
appKey: '{{ env('VITE_REVERB_APP_KEY') }}',
11+
host: '{{ env('VITE_REVERB_HOST') }}',
12+
port: {{ env('VITE_REVERB_PORT') }},
13+
scheme: '{{ env('VITE_REVERB_SCHEME') }}',
14+
};
15+
</script>
16+
17+
@vite(['resources/css/app.css', 'resources/js/app.js'])
18+
19+
<meta name="csrf-token" content="{{ csrf_token() }}">
20+
21+
<title>
22+
@setting('name')
23+
</title>
24+
@if(App\Models\Setting::fetch('favicon'))
25+
<link rel="shortcut icon" href="@setting('favicon')"/>
26+
@endif
27+
@include('partials._theme')
28+
</head>
29+
<body class="vh-100 overflow-hidden m-0 p-0 bg-black">
30+
<div id="app">
31+
<youtube-player
32+
code="{{ $party->code }}"
33+
>
34+
</youtube-player>
35+
</div>
36+
@stack('footer')
37+
</body>
38+
</html>

0 commit comments

Comments
 (0)