DynamicHLSPlaylist::getProcessedPlaylist()   A
last analyzed

Complexity

Conditions 4
Paths 1

Size

Total Lines 21
Code Lines 13

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 4
eloc 13
c 1
b 0
f 0
nc 1
nop 1
dl 0
loc 21
rs 9.8333
1
<?php
2
3
namespace ProtoneMedia\LaravelFFMpeg\Http;
4
5
use Illuminate\Contracts\Support\Responsable;
6
use Illuminate\Support\Collection;
7
use Illuminate\Support\Facades\Response;
8
use Illuminate\Support\Str;
9
use ProtoneMedia\LaravelFFMpeg\Filesystem\Disk;
10
use ProtoneMedia\LaravelFFMpeg\Filesystem\Media;
11
12
class DynamicHLSPlaylist implements Responsable
13
{
14
    /**
15
     * @var \ProtoneMedia\LaravelFFMpeg\Filesystem\Disk
16
     */
17
    private $disk;
18
19
    /**
20
     * @var \ProtoneMedia\LaravelFFMpeg\Filesystem\Media
21
     */
22
    private $media;
23
24
    /**
25
     * Callable to retrieve the path to the given key.
26
     *
27
     * @var callable
28
     */
29
    private $keyResolver;
30
31
    /**
32
     * Callable to retrieve the path to the given media.
33
     *
34
     * @var callable
35
     */
36
    private $mediaResolver;
37
38
    /**
39
     * @var array
40
     */
41
    private $keyCache = [];
42
43
    /**
44
     * @var array
45
     */
46
    private $playlistCache = [];
47
48
    /**
49
     * @var array
50
     */
51
    private $mediaCache = [];
52
53
    /**
54
     * Uses the 'filesystems.default' disk as default.
55
     */
56
    public function __construct($disk = null)
57
    {
58
        $this->fromDisk($disk ?: config('filesystems.default'));
59
    }
60
61
    /**
62
     * Set the disk to open files from.
63
     */
64
    public function fromDisk($disk): self
65
    {
66
        $this->disk = Disk::make($disk);
67
68
        return $this;
69
    }
70
71
    /**
72
     * Instantiates a Media object for the given path and clears the cache.
73
     */
74
    public function open(string $path): self
75
    {
76
        $this->media = Media::make($this->disk, $path);
77
78
        $this->keyCache      = [];
79
        $this->playlistCache = [];
80
        $this->mediaCache    = [];
81
82
        return $this;
83
    }
84
85
    public function setMediaUrlResolver(callable $mediaResolver): self
86
    {
87
        $this->mediaResolver = $mediaResolver;
88
89
        return $this;
90
    }
91
92
    public function setPlaylistUrlResolver(callable $playlistResolver): self
93
    {
94
        $this->playlistResolver = $playlistResolver;
0 ignored issues
show
Bug Best Practice introduced by
The property playlistResolver does not exist. Although not strictly required by PHP, it is generally a best practice to declare properties explicitly.
Loading history...
95
96
        return $this;
97
    }
98
99
    public function setKeyUrlResolver(callable $keyResolver): self
100
    {
101
        $this->keyResolver = $keyResolver;
102
103
        return $this;
104
    }
105
106
    /**
107
     * Returns the resolved key filename from the cache or resolves it.
108
     *
109
     * @param string $key
110
     * @return string
111
     */
112
    private function resolveKeyFilename(string $key): string
113
    {
114
        if (array_key_exists($key, $this->keyCache)) {
115
            return $this->keyCache[$key];
116
        }
117
118
        return $this->keyCache[$key] = call_user_func($this->keyResolver, $key);
119
    }
120
121
    /**
122
     * Returns the resolved media filename from the cache or resolves it.
123
     *
124
     * @param string $key
125
     * @return string
126
     */
127
    private function resolveMediaFilename(string $media): string
128
    {
129
        if (array_key_exists($media, $this->mediaCache)) {
130
            return $this->mediaCache[$media];
131
        }
132
133
        return $this->mediaCache[$media] = call_user_func($this->mediaResolver, $media);
134
    }
135
136
    /**
137
     * Returns the resolved playlist filename from the cache or resolves it.
138
     *
139
     * @param string $key
140
     * @return string
141
     */
142
    private function resolvePlaylistFilename(string $playlist): string
143
    {
144
        if (array_key_exists($playlist, $this->playlistCache)) {
145
            return $this->playlistCache[$playlist];
146
        }
147
148
        return $this->playlistCache[$playlist] = call_user_func($this->playlistResolver, $playlist);
149
    }
150
151
    /**
152
     * Parses the lines into a Collection
153
     *
154
     * @param string $lines
155
     * @return \Illuminate\Support\Collection
156
     */
157
    public static function parseLines(string $lines): Collection
158
    {
159
        return Collection::make(preg_split('/\n|\r\n?/', $lines));
160
    }
161
162
    /**
163
     * Returns a boolean wether the line contains a .M3U8 playlist filename
164
     * or a .TS segment filename.
165
     *
166
     * @param string $line
167
     * @return boolean
168
     */
169
    private static function lineHasMediaFilename(string $line): bool
170
    {
171
        return !Str::startsWith($line, '#') && Str::endsWith($line, ['.m3u8', '.ts']);
172
    }
173
174
    /**
175
     * Returns the filename of the encryption key.
176
     *
177
     * @param string $line
178
     * @return string|null
179
     */
180
    private static function extractKeyFromExtLine(string $line): ?string
181
    {
182
        preg_match_all('/#EXT-X-KEY:METHOD=AES-128,URI="([a-zA-Z0-9-_\/:]+.key)",IV=[a-z0-9]+/', $line, $matches);
183
184
        return $matches[1][0] ?? null;
185
    }
186
187
    /**
188
     * Returns the processed content of the playlist.
189
     *
190
     * @return string
191
     */
192
    public function get(): string
193
    {
194
        return $this->getProcessedPlaylist($this->media->getPath());
195
    }
196
197
    /**
198
     * Returns a collection of all processed segment playlists
199
     * and the processed main playlist.
200
     *
201
     * @return \Illuminate\Support\Collection
202
     */
203
    public function all(): Collection
204
    {
205
        return static::parseLines(
206
            $this->disk->get($this->media->getPath())
0 ignored issues
show
Bug introduced by
The method get() does not exist on ProtoneMedia\LaravelFFMpeg\Filesystem\Disk. Since you implemented __call, consider adding a @method annotation. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

206
            $this->disk->/** @scrutinizer ignore-call */ 
207
                         get($this->media->getPath())
Loading history...
207
        )->filter(function ($line) {
208
            return static::lineHasMediaFilename($line);
209
        })->mapWithKeys(function ($segmentPlaylist) {
210
            return [$segmentPlaylist => $this->getProcessedPlaylist($segmentPlaylist)];
211
        })->prepend(
212
            $this->getProcessedPlaylist($this->media->getPath()),
213
            $this->media->getPath()
214
        );
215
    }
216
217
    /**
218
     * Processes the given playlist.
219
     *
220
     * @param string $playlistPath
221
     * @return string
222
     */
223
    public function getProcessedPlaylist(string $playlistPath): string
224
    {
225
        return static::parseLines($this->disk->get($playlistPath))->map(function (string $line) {
226
            if (static::lineHasMediaFilename($line)) {
227
                return Str::endsWith($line, '.m3u8')
228
                    ? $this->resolvePlaylistFilename($line)
229
                    : $this->resolveMediaFilename($line);
230
            }
231
232
            $key = static::extractKeyFromExtLine($line);
233
234
            if (!$key) {
235
                return $line;
236
            }
237
238
            return str_replace(
239
                '#EXT-X-KEY:METHOD=AES-128,URI="' . $key . '"',
240
                '#EXT-X-KEY:METHOD=AES-128,URI="' . $this->resolveKeyFilename($key) . '"',
241
                $line
242
            );
243
        })->implode(PHP_EOL);
244
    }
245
246
    public function toResponse($request)
247
    {
248
        return Response::make($this->get(), 200, [
249
            'Content-Type' => 'application/vnd.apple.mpegurl',
250
        ]);
251
    }
252
}
253