/src/mpv/common/playlist.c
Line | Count | Source |
1 | | /* |
2 | | * This file is part of mpv. |
3 | | * |
4 | | * mpv is free software; you can redistribute it and/or |
5 | | * modify it under the terms of the GNU Lesser General Public |
6 | | * License as published by the Free Software Foundation; either |
7 | | * version 2.1 of the License, or (at your option) any later version. |
8 | | * |
9 | | * mpv is distributed in the hope that it will be useful, |
10 | | * but WITHOUT ANY WARRANTY; without even the implied warranty of |
11 | | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
12 | | * GNU Lesser General Public License for more details. |
13 | | * |
14 | | * You should have received a copy of the GNU Lesser General Public |
15 | | * License along with mpv. If not, see <http://www.gnu.org/licenses/>. |
16 | | */ |
17 | | |
18 | | #include <assert.h> |
19 | | #include "playlist.h" |
20 | | #include "common/common.h" |
21 | | #include "common/global.h" |
22 | | #include "common/msg.h" |
23 | | #include "misc/random.h" |
24 | | #include "mpv_talloc.h" |
25 | | #include "options/path.h" |
26 | | |
27 | | #include "demux/demux.h" |
28 | | #include "stream/stream.h" |
29 | | |
30 | | struct playlist_entry *playlist_entry_new(const char *filename) |
31 | 400k | { |
32 | 400k | struct playlist_entry *e = talloc_zero(NULL, struct playlist_entry); |
33 | 400k | char *local_filename = mp_file_url_to_filename(e, bstr0(filename)); |
34 | 400k | e->filename = mp_normalize_path(e, local_filename ? local_filename : filename); |
35 | 400k | talloc_free(local_filename); |
36 | 400k | e->stream_flags = STREAM_ORIGIN_DIRECT; |
37 | 400k | e->original_index = -1; |
38 | 400k | return e; |
39 | 400k | } |
40 | | |
41 | | void playlist_entry_add_param(struct playlist_entry *e, bstr name, bstr value) |
42 | 5.30k | { |
43 | 5.30k | struct playlist_param p = {bstrdup(e, name), bstrdup(e, value)}; |
44 | 5.30k | MP_TARRAY_APPEND(e, e->params, e->num_params, p); |
45 | 5.30k | } |
46 | | |
47 | | void playlist_entry_add_params(struct playlist_entry *e, |
48 | | struct playlist_param *params, |
49 | | int num_params) |
50 | 986 | { |
51 | 6.29k | for (int n = 0; n < num_params; n++) |
52 | 5.30k | playlist_entry_add_param(e, params[n].name, params[n].value); |
53 | 986 | } |
54 | | |
55 | | static void playlist_update_indexes(struct playlist *pl, int start, int end) |
56 | 434k | { |
57 | 434k | start = MPMAX(start, 0); |
58 | 434k | end = end < 0 ? pl->num_entries : MPMIN(end, pl->num_entries); |
59 | | |
60 | 1.08M | for (int n = start; n < end; n++) |
61 | 652k | pl->entries[n]->pl_index = n; |
62 | 434k | } |
63 | | |
64 | | // Inserts the entry so that it takes "at"'s place, shifting "at" and all |
65 | | // further entries to the right (or append to end, if at==NULL). |
66 | | void playlist_insert_at(struct playlist *pl, struct playlist_entry *add, |
67 | | struct playlist_entry *at) |
68 | 400k | { |
69 | 400k | mp_assert(add->filename); |
70 | 400k | mp_assert(!at || at->pl == pl); |
71 | | |
72 | 400k | int index = at ? at->pl_index : pl->num_entries; |
73 | 400k | MP_TARRAY_INSERT_AT(pl, pl->entries, pl->num_entries, index, add); |
74 | | |
75 | 400k | add->pl = pl; |
76 | 400k | add->pl_index = index; |
77 | 400k | add->id = ++pl->id_alloc; |
78 | | |
79 | 400k | playlist_update_indexes(pl, index, pl->num_entries); |
80 | | |
81 | 400k | talloc_steal(pl, add); |
82 | 400k | } |
83 | | |
84 | | void playlist_entry_unref(struct playlist_entry *e) |
85 | 122k | { |
86 | 122k | e->reserved--; |
87 | 122k | if (e->reserved < 0) { |
88 | 23.8k | mp_assert(!e->pl); |
89 | 23.8k | talloc_free(e); |
90 | 23.8k | } |
91 | 122k | } |
92 | | |
93 | | void playlist_remove(struct playlist *pl, struct playlist_entry *entry) |
94 | 23.8k | { |
95 | 23.8k | mp_assert(pl && entry->pl == pl); |
96 | | |
97 | 23.8k | if (pl->current == entry) { |
98 | 10.4k | pl->current = playlist_entry_get_rel(entry, 1); |
99 | 10.4k | pl->current_was_replaced = true; |
100 | 10.4k | } |
101 | | |
102 | 23.8k | MP_TARRAY_REMOVE_AT(pl->entries, pl->num_entries, entry->pl_index); |
103 | 23.8k | playlist_update_indexes(pl, entry->pl_index, -1); |
104 | | |
105 | 23.8k | entry->pl = NULL; |
106 | 23.8k | entry->pl_index = -1; |
107 | 23.8k | ta_set_parent(entry, NULL); |
108 | | |
109 | 23.8k | entry->removed = true; |
110 | 23.8k | playlist_entry_unref(entry); |
111 | 23.8k | } |
112 | | |
113 | | void playlist_clear(struct playlist *pl) |
114 | 251k | { |
115 | 265k | for (int n = pl->num_entries - 1; n >= 0; n--) |
116 | 13.4k | playlist_remove(pl, pl->entries[n]); |
117 | 251k | mp_assert(!pl->current); |
118 | 251k | pl->current_was_replaced = false; |
119 | 251k | pl->playlist_completed = false; |
120 | 251k | pl->playlist_started = false; |
121 | 251k | TA_FREEP(&pl->playlist_dir); |
122 | 251k | } |
123 | | |
124 | | void playlist_clear_except_current(struct playlist *pl) |
125 | 0 | { |
126 | 0 | for (int n = pl->num_entries - 1; n >= 0; n--) { |
127 | 0 | if (pl->entries[n] != pl->current) |
128 | 0 | playlist_remove(pl, pl->entries[n]); |
129 | 0 | } |
130 | 0 | pl->playlist_completed = false; |
131 | 0 | pl->playlist_started = false; |
132 | 0 | } |
133 | | |
134 | | // Moves the entry so that it takes "at"'s place (or move to end, if at==NULL). |
135 | | void playlist_move(struct playlist *pl, struct playlist_entry *entry, |
136 | | struct playlist_entry *at) |
137 | 0 | { |
138 | 0 | if (entry == at) |
139 | 0 | return; |
140 | | |
141 | 0 | mp_assert(entry && entry->pl == pl); |
142 | 0 | mp_assert(!at || at->pl == pl); |
143 | | |
144 | 0 | int index = at ? at->pl_index : pl->num_entries; |
145 | 0 | MP_TARRAY_INSERT_AT(pl, pl->entries, pl->num_entries, index, entry); |
146 | | |
147 | 0 | int old_index = entry->pl_index; |
148 | 0 | if (old_index >= index) |
149 | 0 | old_index += 1; |
150 | 0 | MP_TARRAY_REMOVE_AT(pl->entries, pl->num_entries, old_index); |
151 | | |
152 | 0 | playlist_update_indexes(pl, MPMIN(index - 1, old_index - 1), |
153 | 0 | MPMAX(index + 1, old_index + 1)); |
154 | 0 | } |
155 | | |
156 | | void playlist_append_file(struct playlist *pl, const char *filename) |
157 | 312k | { |
158 | 312k | playlist_insert_at(pl, playlist_entry_new(filename), NULL); |
159 | 312k | } |
160 | | |
161 | | void playlist_populate_playlist_path(struct playlist *pl, const char *path) |
162 | 10.8k | { |
163 | 10.8k | char *playlist_path = talloc_strdup(pl, path); |
164 | 244k | for (int n = 0; n < pl->num_entries; n++) { |
165 | 233k | struct playlist_entry *e = pl->entries[n]; |
166 | 233k | e->playlist_path = playlist_path; |
167 | 233k | } |
168 | 10.8k | } |
169 | | |
170 | | void playlist_shuffle(struct playlist *pl) |
171 | 149 | { |
172 | 18.9k | for (int n = 0; n < pl->num_entries; n++) |
173 | 18.8k | pl->entries[n]->original_index = n; |
174 | 149 | mp_rand_state s = mp_rand_seed(0); |
175 | 18.8k | for (int n = 0; n < pl->num_entries - 1; n++) { |
176 | 18.6k | size_t j = mp_rand_in_range32(&s, n, pl->num_entries); |
177 | 18.6k | MPSWAP(struct playlist_entry *, pl->entries[n], pl->entries[j]); |
178 | 18.6k | } |
179 | 149 | playlist_update_indexes(pl, 0, -1); |
180 | 149 | } |
181 | | |
182 | 0 | #define CMP_INT(a, b) ((a) == (b) ? 0 : ((a) > (b) ? 1 : -1)) |
183 | | |
184 | | static int cmp_unshuffle(const void *a, const void *b) |
185 | 0 | { |
186 | 0 | struct playlist_entry *ea = *(struct playlist_entry **)a; |
187 | 0 | struct playlist_entry *eb = *(struct playlist_entry **)b; |
188 | |
|
189 | 0 | if (ea->original_index >= 0 && ea->original_index != eb->original_index) |
190 | 0 | return CMP_INT(ea->original_index, eb->original_index); |
191 | 0 | return CMP_INT(ea->pl_index, eb->pl_index); |
192 | 0 | } |
193 | | |
194 | | void playlist_unshuffle(struct playlist *pl) |
195 | 0 | { |
196 | 0 | if (pl->num_entries) |
197 | 0 | qsort(pl->entries, pl->num_entries, sizeof(pl->entries[0]), cmp_unshuffle); |
198 | 0 | playlist_update_indexes(pl, 0, -1); |
199 | 0 | } |
200 | | |
201 | | // (Explicitly ignores current_was_replaced.) |
202 | | struct playlist_entry *playlist_get_first(struct playlist *pl) |
203 | 144k | { |
204 | 144k | return pl->num_entries ? pl->entries[0] : NULL; |
205 | 144k | } |
206 | | |
207 | | // (Explicitly ignores current_was_replaced.) |
208 | | struct playlist_entry *playlist_get_last(struct playlist *pl) |
209 | 232k | { |
210 | 232k | return pl->num_entries ? pl->entries[pl->num_entries - 1] : NULL; |
211 | 232k | } |
212 | | |
213 | | struct playlist_entry *playlist_get_next(struct playlist *pl, int direction) |
214 | 326k | { |
215 | 326k | mp_assert(direction == -1 || direction == +1); |
216 | 326k | if (!pl->current && pl->playlist_completed && direction < 0) { |
217 | 0 | return playlist_entry_from_index(pl, pl->num_entries - 1); |
218 | 326k | } else if (!pl->current && !pl->playlist_started && direction > 0) { |
219 | 0 | return playlist_entry_from_index(pl, 0); |
220 | 326k | } else if (!pl->current) { |
221 | 0 | return NULL; |
222 | 0 | } |
223 | 326k | mp_assert(pl->current->pl == pl); |
224 | 326k | if (direction < 0) |
225 | 0 | return playlist_entry_get_rel(pl->current, -1); |
226 | 326k | return pl->current_was_replaced ? pl->current : |
227 | 326k | playlist_entry_get_rel(pl->current, 1); |
228 | 326k | } |
229 | | |
230 | | // (Explicitly ignores current_was_replaced.) |
231 | | struct playlist_entry *playlist_entry_get_rel(struct playlist_entry *e, |
232 | | int direction) |
233 | 327k | { |
234 | 327k | mp_assert(direction == -1 || direction == +1); |
235 | 327k | if (!e->pl) |
236 | 0 | return NULL; |
237 | 327k | return playlist_entry_from_index(e->pl, e->pl_index + direction); |
238 | 327k | } |
239 | | |
240 | | struct playlist_entry *playlist_get_first_in_next_playlist(struct playlist *pl, |
241 | | int direction) |
242 | 0 | { |
243 | 0 | struct playlist_entry *entry = playlist_get_next(pl, direction); |
244 | 0 | if (!entry) |
245 | 0 | return NULL; |
246 | | |
247 | 0 | while (entry && entry->playlist_path && pl->current->playlist_path && |
248 | 0 | strcmp(entry->playlist_path, pl->current->playlist_path) == 0) |
249 | 0 | entry = playlist_entry_get_rel(entry, direction); |
250 | |
|
251 | 0 | if (direction < 0) |
252 | 0 | entry = playlist_get_first_in_same_playlist(entry, |
253 | 0 | pl->current->playlist_path); |
254 | |
|
255 | 0 | return entry; |
256 | 0 | } |
257 | | |
258 | | struct playlist_entry *playlist_get_first_in_same_playlist( |
259 | | struct playlist_entry *entry, char *current_playlist_path) |
260 | 0 | { |
261 | 0 | void *tmp = talloc_new(NULL); |
262 | |
|
263 | 0 | if (!entry || !entry->playlist_path) |
264 | 0 | goto exit; |
265 | | |
266 | | // Don't go to the beginning of the playlist when the current playlist-path |
267 | | // starts with the previous playlist-path, e.g. with mpv --loop-playlist |
268 | | // archive_dir/, which expands to archive_dir/{1..9}.zip, the current |
269 | | // playlist path "archive_dir/1.zip" begins with the playlist-path |
270 | | // "archive_dir/" of {2..9}.zip, so go to 9.zip instead of 2.zip. But |
271 | | // playlist-prev-playlist from e.g. the directory "foobar" to the directory |
272 | | // "foo" should still go to the first entry in "foo/", and this should all |
273 | | // work whether mpv's arguments have trailing slashes or not, e.g. in the |
274 | | // first example: |
275 | | // mpv archive_dir results in the playlist-paths "archive_dir/1.zip" and |
276 | | // "archive_dir" |
277 | | // mpv archive_dir/ in "archive_dir/1.zip" and "archive_dir/" |
278 | | // mpv archive_dir// in "archive_dir//1.zip" and "archive_dir//" |
279 | | // Always adding a separator to entry->playlist_path to fix the foobar foo |
280 | | // case would break the previous 2 cases instead. Stripping the separator |
281 | | // from entry->playlist_path if present and appending it again makes this |
282 | | // work in all cases. |
283 | 0 | char* playlist_path = talloc_strdup(tmp, entry->playlist_path); |
284 | 0 | mp_path_strip_trailing_separator(playlist_path); |
285 | 0 | if (bstr_startswith(bstr0(current_playlist_path), |
286 | 0 | bstr0(talloc_strdup_append(playlist_path, "/"))) |
287 | | #if HAVE_DOS_PATHS |
288 | | || |
289 | | bstr_startswith(bstr0(current_playlist_path), |
290 | | bstr0(talloc_strdup_append(playlist_path, "\\"))) |
291 | | #endif |
292 | 0 | ) |
293 | 0 | goto exit; |
294 | | |
295 | 0 | struct playlist_entry *prev = playlist_entry_get_rel(entry, -1); |
296 | |
|
297 | 0 | while (prev && prev->playlist_path && |
298 | 0 | strcmp(prev->playlist_path, entry->playlist_path) == 0) { |
299 | 0 | entry = prev; |
300 | 0 | prev = playlist_entry_get_rel(entry, -1); |
301 | 0 | } |
302 | |
|
303 | 0 | exit: |
304 | 0 | talloc_free(tmp); |
305 | 0 | return entry; |
306 | 0 | } |
307 | | |
308 | | void playlist_set_stream_flags(struct playlist *pl, int flags) |
309 | 10.8k | { |
310 | 244k | for (int n = 0; n < pl->num_entries; n++) |
311 | 233k | pl->entries[n]->stream_flags = flags; |
312 | 10.8k | } |
313 | | |
314 | | int64_t playlist_transfer_entries_to(struct playlist *pl, int dst_index, |
315 | | struct playlist *source_pl) |
316 | 10.4k | { |
317 | 10.4k | mp_assert(pl != source_pl); |
318 | 10.4k | struct playlist_entry *first = playlist_get_first(source_pl); |
319 | | |
320 | 10.4k | int count = source_pl->num_entries; |
321 | 10.4k | MP_TARRAY_INSERT_N_AT(pl, pl->entries, pl->num_entries, dst_index, count); |
322 | | |
323 | 244k | for (int n = 0; n < count; n++) { |
324 | 233k | struct playlist_entry *e = source_pl->entries[n]; |
325 | 233k | e->pl = pl; |
326 | 233k | e->pl_index = dst_index + n; |
327 | 233k | e->id = ++pl->id_alloc; |
328 | 233k | pl->entries[e->pl_index] = e; |
329 | 233k | talloc_steal(pl, e); |
330 | 233k | talloc_steal(pl, e->playlist_path); |
331 | 233k | } |
332 | | |
333 | 10.4k | playlist_update_indexes(pl, dst_index + count, -1); |
334 | 10.4k | source_pl->num_entries = 0; |
335 | | |
336 | 10.4k | pl->playlist_completed = source_pl->playlist_completed; |
337 | 10.4k | pl->playlist_started = source_pl->playlist_started; |
338 | | |
339 | 10.4k | return first ? first->id : 0; |
340 | 10.4k | } |
341 | | |
342 | | // Move all entries from source_pl to pl, appending them after the current entry |
343 | | // of pl. source_pl will be empty, and all entries have changed ownership to pl. |
344 | | // Return the new ID of the first added entry within pl (0 if source_pl was |
345 | | // empty). The IDs of all added entries increase by 1 each entry (you can |
346 | | // predict the ID of the last entry). |
347 | | int64_t playlist_transfer_entries(struct playlist *pl, struct playlist *source_pl) |
348 | 10.4k | { |
349 | | |
350 | 10.4k | int add_at = pl->num_entries; |
351 | 10.4k | if (pl->current) { |
352 | 10.4k | add_at = pl->current->pl_index + 1; |
353 | 10.4k | if (pl->current_was_replaced) |
354 | 0 | add_at += 1; |
355 | 10.4k | } |
356 | 10.4k | mp_assert(add_at >= 0); |
357 | 10.4k | mp_assert(add_at <= pl->num_entries); |
358 | | |
359 | 10.4k | return playlist_transfer_entries_to(pl, add_at, source_pl); |
360 | 10.4k | } |
361 | | |
362 | | int64_t playlist_append_entries(struct playlist *pl, struct playlist *source_pl) |
363 | 0 | { |
364 | 0 | return playlist_transfer_entries_to(pl, pl->num_entries, source_pl); |
365 | 0 | } |
366 | | |
367 | | // Return number of entries between list start and e. |
368 | | // Return -1 if e is not on the list, or if e is NULL. |
369 | | int playlist_entry_to_index(struct playlist *pl, struct playlist_entry *e) |
370 | 89.2k | { |
371 | 89.2k | if (!e || e->pl != pl) |
372 | 582 | return -1; |
373 | 88.6k | return e->pl_index; |
374 | 89.2k | } |
375 | | |
376 | | int playlist_entry_count(struct playlist *pl) |
377 | 4.72k | { |
378 | 4.72k | return pl->num_entries; |
379 | 4.72k | } |
380 | | |
381 | | // Return entry for which playlist_entry_to_index() would return index. |
382 | | // Return NULL if not found. |
383 | | struct playlist_entry *playlist_entry_from_index(struct playlist *pl, int index) |
384 | 384k | { |
385 | 384k | return index >= 0 && index < pl->num_entries ? pl->entries[index] : NULL; |
386 | 384k | } |
387 | | |
388 | | struct playlist *playlist_parse_file(const char *file, struct mp_cancel *cancel, |
389 | | struct mpv_global *global) |
390 | 0 | { |
391 | 0 | struct mp_log *log = mp_log_new(NULL, global->log, "!playlist_parser"); |
392 | 0 | mp_verbose(log, "Parsing playlist file %s...\n", file); |
393 | |
|
394 | 0 | char *path = mp_get_user_path(NULL, global, file); |
395 | 0 | struct demuxer_params p = { |
396 | 0 | .force_format = "playlist", |
397 | 0 | .stream_flags = STREAM_ORIGIN_DIRECT, |
398 | 0 | }; |
399 | 0 | struct demuxer *d = demux_open_url(path, &p, cancel, global); |
400 | 0 | struct playlist *ret = NULL; |
401 | 0 | if (!d) |
402 | 0 | goto done; |
403 | | |
404 | 0 | if (d && d->playlist) { |
405 | 0 | ret = talloc_zero(NULL, struct playlist); |
406 | 0 | playlist_populate_playlist_path(d->playlist, file); |
407 | 0 | playlist_transfer_entries(ret, d->playlist); |
408 | 0 | if (d->filetype && strcmp(d->filetype, "hls") == 0) { |
409 | 0 | mp_warn(log, "This might be a HLS stream. For correct operation, " |
410 | 0 | "pass it to the player\ndirectly. Don't use --playlist.\n"); |
411 | 0 | } |
412 | 0 | } |
413 | 0 | demux_free(d); |
414 | |
|
415 | 0 | if (ret) { |
416 | 0 | mp_verbose(log, "Playlist successfully parsed\n"); |
417 | 0 | } else { |
418 | 0 | mp_err(log, "Error while parsing playlist\n"); |
419 | 0 | } |
420 | |
|
421 | 0 | if (ret && !ret->num_entries) |
422 | 0 | mp_warn(log, "Warning: empty playlist\n"); |
423 | |
|
424 | 0 | done: |
425 | 0 | talloc_free(log); |
426 | 0 | talloc_free(path); |
427 | 0 | return ret; |
428 | 0 | } |
429 | | |
430 | | void playlist_set_current(struct playlist *pl) |
431 | 0 | { |
432 | 0 | if (!pl->playlist_dir) |
433 | 0 | return; |
434 | | |
435 | 0 | for (int i = 0; i < pl->num_entries; ++i) { |
436 | 0 | if (pl->entries[i]->playlist_path && |
437 | 0 | !strcmp(pl->entries[i]->filename, pl->entries[i]->playlist_path)) { |
438 | 0 | pl->current = pl->entries[i]; |
439 | 0 | break; |
440 | 0 | } |
441 | 0 | } |
442 | 0 | } |