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 "config.h"
20 #include "playlist.h"
21 #include "common/common.h"
22 #include "common/global.h"
23 #include "common/msg.h"
24 #include "mpv_talloc.h"
25 #include "options/path.h"
26
27 #include "demux/demux.h"
28 #include "stream/stream.h"
29
playlist_entry_new(const char * filename)30 struct playlist_entry *playlist_entry_new(const char *filename)
31 {
32 struct playlist_entry *e = talloc_zero(NULL, struct playlist_entry);
33 char *local_filename = mp_file_url_to_filename(e, bstr0(filename));
34 e->filename = local_filename ? local_filename : talloc_strdup(e, filename);
35 e->stream_flags = STREAM_ORIGIN_DIRECT;
36 e->original_index = -1;
37 return e;
38 }
39
playlist_entry_add_param(struct playlist_entry * e,bstr name,bstr value)40 void playlist_entry_add_param(struct playlist_entry *e, bstr name, bstr value)
41 {
42 struct playlist_param p = {bstrdup(e, name), bstrdup(e, value)};
43 MP_TARRAY_APPEND(e, e->params, e->num_params, p);
44 }
45
playlist_entry_add_params(struct playlist_entry * e,struct playlist_param * params,int num_params)46 void playlist_entry_add_params(struct playlist_entry *e,
47 struct playlist_param *params,
48 int num_params)
49 {
50 for (int n = 0; n < num_params; n++)
51 playlist_entry_add_param(e, params[n].name, params[n].value);
52 }
53
playlist_update_indexes(struct playlist * pl,int start,int end)54 static void playlist_update_indexes(struct playlist *pl, int start, int end)
55 {
56 start = MPMAX(start, 0);
57 end = end < 0 ? pl->num_entries : MPMIN(end, pl->num_entries);
58
59 for (int n = start; n < end; n++)
60 pl->entries[n]->pl_index = n;
61 }
62
playlist_add(struct playlist * pl,struct playlist_entry * add)63 void playlist_add(struct playlist *pl, struct playlist_entry *add)
64 {
65 assert(add->filename);
66 MP_TARRAY_APPEND(pl, pl->entries, pl->num_entries, add);
67 add->pl = pl;
68 add->pl_index = pl->num_entries - 1;
69 add->id = ++pl->id_alloc;
70 talloc_steal(pl, add);
71 }
72
playlist_entry_unref(struct playlist_entry * e)73 void playlist_entry_unref(struct playlist_entry *e)
74 {
75 e->reserved--;
76 if (e->reserved < 0) {
77 assert(!e->pl);
78 talloc_free(e);
79 }
80 }
81
playlist_remove(struct playlist * pl,struct playlist_entry * entry)82 void playlist_remove(struct playlist *pl, struct playlist_entry *entry)
83 {
84 assert(pl && entry->pl == pl);
85
86 if (pl->current == entry) {
87 pl->current = playlist_entry_get_rel(entry, 1);
88 pl->current_was_replaced = true;
89 }
90
91 MP_TARRAY_REMOVE_AT(pl->entries, pl->num_entries, entry->pl_index);
92 playlist_update_indexes(pl, entry->pl_index, -1);
93
94 entry->pl = NULL;
95 entry->pl_index = -1;
96 ta_set_parent(entry, NULL);
97
98 entry->removed = true;
99 playlist_entry_unref(entry);
100 }
101
playlist_clear(struct playlist * pl)102 void playlist_clear(struct playlist *pl)
103 {
104 for (int n = pl->num_entries - 1; n >= 0; n--)
105 playlist_remove(pl, pl->entries[n]);
106 assert(!pl->current);
107 pl->current_was_replaced = false;
108 }
109
playlist_clear_except_current(struct playlist * pl)110 void playlist_clear_except_current(struct playlist *pl)
111 {
112 for (int n = pl->num_entries - 1; n >= 0; n--) {
113 if (pl->entries[n] != pl->current)
114 playlist_remove(pl, pl->entries[n]);
115 }
116 }
117
118 // Moves the entry so that it takes "at"'s place (or move to end, if at==NULL).
playlist_move(struct playlist * pl,struct playlist_entry * entry,struct playlist_entry * at)119 void playlist_move(struct playlist *pl, struct playlist_entry *entry,
120 struct playlist_entry *at)
121 {
122 if (entry == at)
123 return;
124
125 assert(entry && entry->pl == pl);
126 assert(!at || at->pl == pl);
127
128 int index = at ? at->pl_index : pl->num_entries;
129 MP_TARRAY_INSERT_AT(pl, pl->entries, pl->num_entries, index, entry);
130
131 int old_index = entry->pl_index;
132 if (old_index >= index)
133 old_index += 1;
134 MP_TARRAY_REMOVE_AT(pl->entries, pl->num_entries, old_index);
135
136 playlist_update_indexes(pl, MPMIN(index - 1, old_index - 1),
137 MPMAX(index + 1, old_index + 1));
138 }
139
playlist_add_file(struct playlist * pl,const char * filename)140 void playlist_add_file(struct playlist *pl, const char *filename)
141 {
142 playlist_add(pl, playlist_entry_new(filename));
143 }
144
playlist_shuffle(struct playlist * pl)145 void playlist_shuffle(struct playlist *pl)
146 {
147 for (int n = 0; n < pl->num_entries; n++)
148 pl->entries[n]->original_index = n;
149 for (int n = 0; n < pl->num_entries - 1; n++) {
150 int j = (int)((double)(pl->num_entries - n) * rand() / (RAND_MAX + 1.0));
151 MPSWAP(struct playlist_entry *, pl->entries[n], pl->entries[n + j]);
152 }
153 playlist_update_indexes(pl, 0, -1);
154 }
155
156 #define CMP_INT(a, b) ((a) == (b) ? 0 : ((a) > (b) ? 1 : -1))
157
cmp_unshuffle(const void * a,const void * b)158 static int cmp_unshuffle(const void *a, const void *b)
159 {
160 struct playlist_entry *ea = *(struct playlist_entry **)a;
161 struct playlist_entry *eb = *(struct playlist_entry **)b;
162
163 if (ea->original_index >= 0 && ea->original_index != eb->original_index)
164 return CMP_INT(ea->original_index, eb->original_index);
165 return CMP_INT(ea->pl_index, eb->pl_index);
166 }
167
playlist_unshuffle(struct playlist * pl)168 void playlist_unshuffle(struct playlist *pl)
169 {
170 if (pl->num_entries)
171 qsort(pl->entries, pl->num_entries, sizeof(pl->entries[0]), cmp_unshuffle);
172 playlist_update_indexes(pl, 0, -1);
173 }
174
175 // (Explicitly ignores current_was_replaced.)
playlist_get_first(struct playlist * pl)176 struct playlist_entry *playlist_get_first(struct playlist *pl)
177 {
178 return pl->num_entries ? pl->entries[0] : NULL;
179 }
180
181 // (Explicitly ignores current_was_replaced.)
playlist_get_last(struct playlist * pl)182 struct playlist_entry *playlist_get_last(struct playlist *pl)
183 {
184 return pl->num_entries ? pl->entries[pl->num_entries - 1] : NULL;
185 }
186
playlist_get_next(struct playlist * pl,int direction)187 struct playlist_entry *playlist_get_next(struct playlist *pl, int direction)
188 {
189 assert(direction == -1 || direction == +1);
190 if (!pl->current)
191 return NULL;
192 assert(pl->current->pl == pl);
193 if (direction < 0)
194 return playlist_entry_get_rel(pl->current, -1);
195 return pl->current_was_replaced ? pl->current :
196 playlist_entry_get_rel(pl->current, 1);
197 }
198
199 // (Explicitly ignores current_was_replaced.)
playlist_entry_get_rel(struct playlist_entry * e,int direction)200 struct playlist_entry *playlist_entry_get_rel(struct playlist_entry *e,
201 int direction)
202 {
203 assert(direction == -1 || direction == +1);
204 if (!e->pl)
205 return NULL;
206 return playlist_entry_from_index(e->pl, e->pl_index + direction);
207 }
208
playlist_add_base_path(struct playlist * pl,bstr base_path)209 void playlist_add_base_path(struct playlist *pl, bstr base_path)
210 {
211 if (base_path.len == 0 || bstrcmp0(base_path, ".") == 0)
212 return;
213 for (int n = 0; n < pl->num_entries; n++) {
214 struct playlist_entry *e = pl->entries[n];
215 if (!mp_is_url(bstr0(e->filename))) {
216 char *new_file = mp_path_join_bstr(e, base_path, bstr0(e->filename));
217 talloc_free(e->filename);
218 e->filename = new_file;
219 }
220 }
221 }
222
223 // Add redirected_from as new redirect entry to each item in pl.
playlist_add_redirect(struct playlist * pl,const char * redirected_from)224 void playlist_add_redirect(struct playlist *pl, const char *redirected_from)
225 {
226 for (int n = 0; n < pl->num_entries; n++) {
227 struct playlist_entry *e = pl->entries[n];
228 if (e->num_redirects >= 10) // arbitrary limit for sanity
229 continue;
230 char *s = talloc_strdup(e, redirected_from);
231 if (s)
232 MP_TARRAY_APPEND(e, e->redirects, e->num_redirects, s);
233 }
234 }
235
playlist_set_stream_flags(struct playlist * pl,int flags)236 void playlist_set_stream_flags(struct playlist *pl, int flags)
237 {
238 for (int n = 0; n < pl->num_entries; n++)
239 pl->entries[n]->stream_flags = flags;
240 }
241
playlist_transfer_entries_to(struct playlist * pl,int dst_index,struct playlist * source_pl)242 static int64_t playlist_transfer_entries_to(struct playlist *pl, int dst_index,
243 struct playlist *source_pl)
244 {
245 assert(pl != source_pl);
246 struct playlist_entry *first = playlist_get_first(source_pl);
247
248 int count = source_pl->num_entries;
249 MP_TARRAY_INSERT_N_AT(pl, pl->entries, pl->num_entries, dst_index, count);
250
251 for (int n = 0; n < count; n++) {
252 struct playlist_entry *e = source_pl->entries[n];
253 e->pl = pl;
254 e->pl_index = dst_index + n;
255 e->id = ++pl->id_alloc;
256 pl->entries[e->pl_index] = e;
257 talloc_steal(pl, e);
258 }
259
260 playlist_update_indexes(pl, dst_index + count, -1);
261 source_pl->num_entries = 0;
262
263 return first ? first->id : 0;
264 }
265
266 // Move all entries from source_pl to pl, appending them after the current entry
267 // of pl. source_pl will be empty, and all entries have changed ownership to pl.
268 // Return the new ID of the first added entry within pl (0 if source_pl was
269 // empty). The IDs of all added entries increase by 1 each entry (you can
270 // predict the ID of the last entry).
playlist_transfer_entries(struct playlist * pl,struct playlist * source_pl)271 int64_t playlist_transfer_entries(struct playlist *pl, struct playlist *source_pl)
272 {
273
274 int add_at = pl->num_entries;
275 if (pl->current) {
276 add_at = pl->current->pl_index + 1;
277 if (pl->current_was_replaced)
278 add_at += 1;
279 }
280 assert(add_at >= 0);
281 assert(add_at <= pl->num_entries);
282
283 return playlist_transfer_entries_to(pl, add_at, source_pl);
284 }
285
playlist_append_entries(struct playlist * pl,struct playlist * source_pl)286 int64_t playlist_append_entries(struct playlist *pl, struct playlist *source_pl)
287 {
288 return playlist_transfer_entries_to(pl, pl->num_entries, source_pl);
289 }
290
291 // Return number of entries between list start and e.
292 // Return -1 if e is not on the list, or if e is NULL.
playlist_entry_to_index(struct playlist * pl,struct playlist_entry * e)293 int playlist_entry_to_index(struct playlist *pl, struct playlist_entry *e)
294 {
295 if (!e || e->pl != pl)
296 return -1;
297 return e->pl_index;
298 }
299
playlist_entry_count(struct playlist * pl)300 int playlist_entry_count(struct playlist *pl)
301 {
302 return pl->num_entries;
303 }
304
305 // Return entry for which playlist_entry_to_index() would return index.
306 // Return NULL if not found.
playlist_entry_from_index(struct playlist * pl,int index)307 struct playlist_entry *playlist_entry_from_index(struct playlist *pl, int index)
308 {
309 return index >= 0 && index < pl->num_entries ? pl->entries[index] : NULL;
310 }
311
playlist_parse_file(const char * file,struct mp_cancel * cancel,struct mpv_global * global)312 struct playlist *playlist_parse_file(const char *file, struct mp_cancel *cancel,
313 struct mpv_global *global)
314 {
315 struct mp_log *log = mp_log_new(NULL, global->log, "!playlist_parser");
316 mp_verbose(log, "Parsing playlist file %s...\n", file);
317
318 struct demuxer_params p = {
319 .force_format = "playlist",
320 .stream_flags = STREAM_ORIGIN_DIRECT,
321 };
322 struct demuxer *d = demux_open_url(file, &p, cancel, global);
323 if (!d) {
324 talloc_free(log);
325 return NULL;
326 }
327
328 struct playlist *ret = NULL;
329 if (d && d->playlist) {
330 ret = talloc_zero(NULL, struct playlist);
331 playlist_transfer_entries(ret, d->playlist);
332 if (d->filetype && strcmp(d->filetype, "hls") == 0) {
333 mp_warn(log, "This might be a HLS stream. For correct operation, "
334 "pass it to the player\ndirectly. Don't use --playlist.\n");
335 }
336 }
337 demux_free(d);
338
339 if (ret) {
340 mp_verbose(log, "Playlist successfully parsed\n");
341 } else {
342 mp_err(log, "Error while parsing playlist\n");
343 }
344
345 if (ret && !ret->num_entries)
346 mp_warn(log, "Warning: empty playlist\n");
347
348 talloc_free(log);
349 return ret;
350 }
351