diff options
author | wm4 <wm4@nowhere> | 2013-12-17 00:53:22 +0100 |
---|---|---|
committer | wm4 <wm4@nowhere> | 2013-12-17 00:53:22 +0100 |
commit | e44911142914783c9ec717f329bd9b6a8bb9b70e (patch) | |
tree | 92bb653f7d56553ffd3bb6e5a22ffc0db91142e8 /player/loadfile.c | |
parent | 7dc7b900c622235d595337c988a0c75280084b7c (diff) | |
download | mpv-e44911142914783c9ec717f329bd9b6a8bb9b70e.tar.bz2 mpv-e44911142914783c9ec717f329bd9b6a8bb9b70e.tar.xz |
Move mpvcore/player/ to player/
Diffstat (limited to 'player/loadfile.c')
-rw-r--r-- | player/loadfile.c | 1423 |
1 files changed, 1423 insertions, 0 deletions
diff --git a/player/loadfile.c b/player/loadfile.c new file mode 100644 index 0000000000..08b245a0ff --- /dev/null +++ b/player/loadfile.c @@ -0,0 +1,1423 @@ +/* + * This file is part of MPlayer. + * + * MPlayer is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * MPlayer is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with MPlayer; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include <stddef.h> +#include <stdbool.h> +#include <inttypes.h> +#include <assert.h> + +#include <libavutil/avutil.h> + +#include "config.h" +#include "talloc.h" + +#include "osdep/getch2.h" +#include "osdep/io.h" +#include "osdep/timer.h" + +#include "mpvcore/mp_msg.h" +#include "mpvcore/path.h" +#include "mpvcore/m_config.h" +#include "mpvcore/parser-cfg.h" +#include "mpvcore/playlist.h" +#include "mpvcore/options.h" +#include "mpvcore/m_property.h" +#include "mpvcore/mp_common.h" +#include "mpvcore/resolve.h" +#include "mpvcore/encode.h" +#include "mpvcore/input/input.h" + +#include "audio/mixer.h" +#include "audio/audio.h" +#include "audio/audio_buffer.h" +#include "audio/decode/dec_audio.h" +#include "audio/out/ao.h" +#include "demux/demux.h" +#include "stream/stream.h" +#include "sub/ass_mp.h" +#include "sub/dec_sub.h" +#include "sub/find_subfiles.h" +#include "video/decode/dec_video.h" +#include "video/out/vo.h" + +#include "mp_core.h" +#include "command.h" + +#if HAVE_DVBIN +#include "stream/dvbin.h" +#endif + +void uninit_player(struct MPContext *mpctx, unsigned int mask) +{ + struct MPOpts *opts = mpctx->opts; + + mask &= mpctx->initialized_flags; + + MP_DBG(mpctx, "\n*** uninit(0x%X)\n", mask); + + if (mask & INITIALIZED_ACODEC) { + mpctx->initialized_flags &= ~INITIALIZED_ACODEC; + mixer_uninit_audio(mpctx->mixer); + audio_uninit(mpctx->d_audio); + mpctx->d_audio = NULL; + cleanup_demux_stream(mpctx, STREAM_AUDIO); + } + + if (mask & INITIALIZED_SUB) { + mpctx->initialized_flags &= ~INITIALIZED_SUB; + if (mpctx->d_sub) + sub_reset(mpctx->d_sub); + cleanup_demux_stream(mpctx, STREAM_SUB); + mpctx->d_sub = NULL; // Note: not free'd. + mpctx->osd->dec_sub = NULL; + reset_subtitles(mpctx); + } + + if (mask & INITIALIZED_LIBASS) { + mpctx->initialized_flags &= ~INITIALIZED_LIBASS; +#if HAVE_LIBASS + if (mpctx->ass_renderer) + ass_renderer_done(mpctx->ass_renderer); + mpctx->ass_renderer = NULL; + ass_clear_fonts(mpctx->ass_library); +#endif + } + + if (mask & INITIALIZED_VCODEC) { + mpctx->initialized_flags &= ~INITIALIZED_VCODEC; + if (mpctx->d_video) + video_uninit(mpctx->d_video); + mpctx->d_video = NULL; + cleanup_demux_stream(mpctx, STREAM_VIDEO); + mpctx->sync_audio_to_video = false; + } + + if (mask & INITIALIZED_DEMUXER) { + mpctx->initialized_flags &= ~INITIALIZED_DEMUXER; + assert(!(mpctx->initialized_flags & + (INITIALIZED_VCODEC | INITIALIZED_ACODEC | INITIALIZED_SUB))); + for (int i = 0; i < mpctx->num_tracks; i++) { + talloc_free(mpctx->tracks[i]); + } + mpctx->num_tracks = 0; + for (int t = 0; t < STREAM_TYPE_COUNT; t++) + mpctx->current_track[t] = NULL; + assert(!mpctx->d_video && !mpctx->d_audio && !mpctx->d_sub); + mpctx->master_demuxer = NULL; + for (int i = 0; i < mpctx->num_sources; i++) { + uninit_subs(mpctx->sources[i]); + struct demuxer *demuxer = mpctx->sources[i]; + struct stream *stream = demuxer->stream; + free_demuxer(demuxer); + if (stream != mpctx->stream) + free_stream(stream); + } + talloc_free(mpctx->sources); + mpctx->sources = NULL; + mpctx->demuxer = NULL; + mpctx->num_sources = 0; + talloc_free(mpctx->timeline); + mpctx->timeline = NULL; + mpctx->num_timeline_parts = 0; + talloc_free(mpctx->chapters); + mpctx->chapters = NULL; + mpctx->num_chapters = 0; + mpctx->video_offset = 0; + } + + // kill the cache process: + if (mask & INITIALIZED_STREAM) { + mpctx->initialized_flags &= ~INITIALIZED_STREAM; + if (mpctx->stream) + free_stream(mpctx->stream); + mpctx->stream = NULL; + } + + if (mask & INITIALIZED_VO) { + mpctx->initialized_flags &= ~INITIALIZED_VO; + vo_destroy(mpctx->video_out); + mpctx->video_out = NULL; + } + + if (mask & INITIALIZED_AO) { + struct ao *ao = mpctx->ao; + mpctx->initialized_flags &= ~INITIALIZED_AO; + if (ao) { + bool drain = false; + // Note: with gapless_audio, stop_play is not correctly set + if (opts->gapless_audio || mpctx->stop_play == AT_END_OF_FILE) { + drain = true; + struct mp_audio data; + mp_audio_buffer_peek(ao->buffer, &data); + int samples = ao->buffer_playable_samples; + assert(samples <= data.samples); + if (samples > 0) { + int played = ao_play(ao, data.planes, samples, + AOPLAY_FINAL_CHUNK); + if (played < samples) + MP_WARN(ao, "Audio output truncated at end.\n"); + } + } + ao_uninit(ao, drain); + } + mpctx->ao = NULL; + } + + if (mask & INITIALIZED_PLAYBACK) + mpctx->initialized_flags &= ~INITIALIZED_PLAYBACK; +} + +static void print_stream(struct MPContext *mpctx, struct track *t) +{ + struct sh_stream *s = t->stream; + const char *tname = "?"; + const char *selopt = "?"; + const char *langopt = "?"; + const char *iid = NULL; + switch (t->type) { + case STREAM_VIDEO: + tname = "Video"; selopt = "vid"; langopt = NULL; iid = "VID"; + break; + case STREAM_AUDIO: + tname = "Audio"; selopt = "aid"; langopt = "alang"; iid = "AID"; + break; + case STREAM_SUB: + tname = "Subs"; selopt = "sid"; langopt = "slang"; iid = "SID"; + break; + } + MP_INFO(mpctx, "[stream] %-5s %3s", + tname, mpctx->current_track[t->type] == t ? "(+)" : ""); + MP_INFO(mpctx, " --%s=%d", selopt, t->user_tid); + if (t->lang && langopt) + MP_INFO(mpctx, " --%s=%s", langopt, t->lang); + if (t->default_track) + MP_INFO(mpctx, " (*)"); + if (t->attached_picture) + MP_INFO(mpctx, " [P]"); + if (t->title) + MP_INFO(mpctx, " '%s'", t->title); + const char *codec = s ? s->codec : NULL; + MP_INFO(mpctx, " (%s)", codec ? codec : "<unknown>"); + if (t->is_external) + MP_INFO(mpctx, " (external)"); + MP_INFO(mpctx, "\n"); + // legacy compatibility + if (!iid) + return; + int id = t->user_tid; + mp_msg(MSGT_IDENTIFY, MSGL_INFO, "ID_%s_ID=%d\n", iid, id); + if (t->title) + mp_msg(MSGT_IDENTIFY, MSGL_INFO, "ID_%s_%d_NAME=%s\n", iid, id, t->title); + if (t->lang) + mp_msg(MSGT_IDENTIFY, MSGL_INFO, "ID_%s_%d_LANG=%s\n", iid, id, t->lang); +} + +static void print_file_properties(struct MPContext *mpctx) +{ + mp_msg(MSGT_IDENTIFY, MSGL_INFO, "ID_FILENAME=%s\n", mpctx->filename); + mp_msg(MSGT_IDENTIFY, MSGL_INFO, + "ID_LENGTH=%.2f\n", get_time_length(mpctx)); + int chapter_count = get_chapter_count(mpctx); + if (chapter_count >= 0) { + mp_msg(MSGT_IDENTIFY, MSGL_INFO, "ID_CHAPTERS=%d\n", chapter_count); + for (int i = 0; i < chapter_count; i++) { + mp_msg(MSGT_IDENTIFY, MSGL_INFO, "ID_CHAPTER_ID=%d\n", i); + // print in milliseconds + double time = chapter_start_time(mpctx, i) * 1000.0; + mp_msg(MSGT_IDENTIFY, MSGL_INFO, "ID_CHAPTER_%d_START=%"PRId64"\n", + i, (int64_t)(time < 0 ? -1 : time)); + char *name = chapter_name(mpctx, i); + if (name) { + mp_msg(MSGT_IDENTIFY, MSGL_INFO, "ID_CHAPTER_%d_NAME=%s\n", i, + name); + talloc_free(name); + } + } + } + struct demuxer *demuxer = mpctx->master_demuxer; + if (demuxer->num_editions > 1) + MP_INFO(mpctx, "Playing edition %d of %d (--edition=%d).\n", + demuxer->edition + 1, demuxer->num_editions, demuxer->edition); + for (int t = 0; t < STREAM_TYPE_COUNT; t++) { + for (int n = 0; n < mpctx->num_tracks; n++) + if (mpctx->tracks[n]->type == t) + print_stream(mpctx, mpctx->tracks[n]); + } +} + +struct sh_stream *init_demux_stream(struct MPContext *mpctx, + enum stream_type type) +{ + struct track *track = mpctx->current_track[type]; + struct sh_stream *stream = track ? track->stream : NULL; + mpctx->sh[type] = stream; + if (stream) { + demuxer_switch_track(stream->demuxer, type, stream); + if (track->is_external) { + double pts = get_main_demux_pts(mpctx); + demux_seek(stream->demuxer, pts, SEEK_ABSOLUTE); + } + } + return stream; +} + +void cleanup_demux_stream(struct MPContext *mpctx, enum stream_type type) +{ + struct sh_stream *stream = mpctx->sh[type]; + if (stream) + demuxer_switch_track(stream->demuxer, type, NULL); + mpctx->sh[type] = NULL; +} + +// Switch the demuxers to current track selection. This is possibly important +// for intialization: if something reads packets from the demuxer (like at least +// reinit_audio_chain does, or when seeking), packets from the other streams +// should be queued instead of discarded. So all streams should be enabled +// before the first initialization function is called. +static void preselect_demux_streams(struct MPContext *mpctx) +{ + // Disable all streams, just to be sure no unwanted streams are selected. + for (int n = 0; n < mpctx->num_sources; n++) { + for (int type = 0; type < STREAM_TYPE_COUNT; type++) { + struct track *track = mpctx->current_track[type]; + if (!(track && track->demuxer == mpctx->sources[n] && + demuxer_stream_is_selected(track->demuxer, track->stream))) + demuxer_switch_track(mpctx->sources[n], type, NULL); + } + } + + for (int type = 0; type < STREAM_TYPE_COUNT; type++) { + struct track *track = mpctx->current_track[type]; + if (track && track->stream) + demuxer_switch_track(track->stream->demuxer, type, track->stream); + } +} + +static struct sh_stream *select_fallback_stream(struct demuxer *d, + enum stream_type type, + int index) +{ + struct sh_stream *best_stream = NULL; + for (int n = 0; n < d->num_streams; n++) { + struct sh_stream *s = d->streams[n]; + if (s->type == type) { + best_stream = s; + if (index == 0) + break; + index -= 1; + } + } + return best_stream; +} + +bool timeline_set_part(struct MPContext *mpctx, int i, bool force) +{ + struct timeline_part *p = mpctx->timeline + mpctx->timeline_part; + struct timeline_part *n = mpctx->timeline + i; + mpctx->timeline_part = i; + mpctx->video_offset = n->start - n->source_start; + if (n->source == p->source && !force) + return false; + enum stop_play_reason orig_stop_play = mpctx->stop_play; + if (!mpctx->d_video && mpctx->stop_play == KEEP_PLAYING) + mpctx->stop_play = AT_END_OF_FILE; // let audio uninit drain data + uninit_player(mpctx, INITIALIZED_VCODEC | (mpctx->opts->fixed_vo ? 0 : INITIALIZED_VO) | (mpctx->opts->gapless_audio ? 0 : INITIALIZED_AO) | INITIALIZED_ACODEC | INITIALIZED_SUB); + mpctx->stop_play = orig_stop_play; + + mpctx->demuxer = n->source; + mpctx->stream = mpctx->demuxer->stream; + + // While another timeline was active, the selection of active tracks might + // have been changed - possibly we need to update this source. + for (int x = 0; x < mpctx->num_tracks; x++) { + struct track *track = mpctx->tracks[x]; + if (track->under_timeline) { + track->demuxer = mpctx->demuxer; + track->stream = demuxer_stream_by_demuxer_id(track->demuxer, + track->type, + track->demuxer_id); + // EDL can have mismatched files in the same timeline + if (!track->stream) { + track->stream = select_fallback_stream(track->demuxer, + track->type, + track->user_tid - 1); + } + } + } + preselect_demux_streams(mpctx); + + return true; +} + +// Given pts, switch playback to the corresponding part. +// Return offset within that part. +double timeline_set_from_time(struct MPContext *mpctx, double pts, bool *need_reset) +{ + if (pts < 0) + pts = 0; + for (int i = 0; i < mpctx->num_timeline_parts; i++) { + struct timeline_part *p = mpctx->timeline + i; + if (pts < (p + 1)->start) { + *need_reset = timeline_set_part(mpctx, i, false); + return pts - p->start + p->source_start; + } + } + return -1; +} + +// Map stream number (as used by libdvdread) to MPEG IDs (as used by demuxer). +static int map_id_from_demuxer(struct demuxer *d, enum stream_type type, int id) +{ + if (d->stream->uncached_type == STREAMTYPE_DVD && type == STREAM_SUB) + id = id & 0x1F; + return id; +} + +static int find_new_tid(struct MPContext *mpctx, enum stream_type t) +{ + int new_id = 0; + for (int i = 0; i < mpctx->num_tracks; i++) { + struct track *track = mpctx->tracks[i]; + if (track->type == t) + new_id = MPMAX(new_id, track->user_tid); + } + return new_id + 1; +} + +static struct track *add_stream_track(struct MPContext *mpctx, + struct sh_stream *stream, + bool under_timeline) +{ + for (int i = 0; i < mpctx->num_tracks; i++) { + struct track *track = mpctx->tracks[i]; + if (track->stream == stream) + return track; + // DVD subtitle track that was added later + if (stream->type == STREAM_SUB && track->type == STREAM_SUB && + map_id_from_demuxer(stream->demuxer, stream->type, + stream->demuxer_id) == track->demuxer_id + && !track->stream) + { + track->stream = stream; + track->demuxer_id = stream->demuxer_id; + // Initialize lazily selected track + bool selected = track == mpctx->current_track[STREAM_SUB]; + demuxer_select_track(track->demuxer, stream, selected); + if (selected) + reinit_subs(mpctx); + return track; + } + } + + struct track *track = talloc_ptrtype(NULL, track); + *track = (struct track) { + .type = stream->type, + .user_tid = find_new_tid(mpctx, stream->type), + .demuxer_id = stream->demuxer_id, + .title = stream->title, + .default_track = stream->default_track, + .attached_picture = stream->attached_picture != NULL, + .lang = stream->lang, + .under_timeline = under_timeline, + .demuxer = stream->demuxer, + .stream = stream, + }; + MP_TARRAY_APPEND(mpctx, mpctx->tracks, mpctx->num_tracks, track); + + if (stream->type == STREAM_SUB) + track->preloaded = !!stream->sub->track; + + // Needed for DVD and Blu-ray. + if (!track->lang) { + struct stream_lang_req req = { + .type = track->type, + .id = map_id_from_demuxer(track->demuxer, track->type, + track->demuxer_id) + }; + stream_control(track->demuxer->stream, STREAM_CTRL_GET_LANG, &req); + if (req.name[0]) + track->lang = talloc_strdup(track, req.name); + } + + demuxer_select_track(track->demuxer, stream, false); + + mp_notify(mpctx, MP_EVENT_TRACKS_CHANGED, NULL); + + return track; +} + +void add_demuxer_tracks(struct MPContext *mpctx, struct demuxer *demuxer) +{ + for (int n = 0; n < demuxer->num_streams; n++) + add_stream_track(mpctx, demuxer->streams[n], !!mpctx->timeline); +} + +static void add_dvd_tracks(struct MPContext *mpctx) +{ + struct demuxer *demuxer = mpctx->demuxer; + struct stream *stream = demuxer->stream; + struct stream_dvd_info_req info; + if (stream_control(stream, STREAM_CTRL_GET_DVD_INFO, &info) > 0) { + for (int n = 0; n < info.num_subs; n++) { + struct track *track = talloc_ptrtype(NULL, track); + *track = (struct track) { + .type = STREAM_SUB, + .user_tid = find_new_tid(mpctx, STREAM_SUB), + .demuxer_id = n, + .demuxer = mpctx->demuxer, + }; + MP_TARRAY_APPEND(mpctx, mpctx->tracks, mpctx->num_tracks, track); + + struct stream_lang_req req = {.type = STREAM_SUB, .id = n}; + stream_control(stream, STREAM_CTRL_GET_LANG, &req); + track->lang = talloc_strdup(track, req.name); + + mp_notify(mpctx, MP_EVENT_TRACKS_CHANGED, NULL); + } + } + demuxer_enable_autoselect(demuxer); +} + +// Result numerically higher => better match. 0 == no match. +static int match_lang(char **langs, char *lang) +{ + for (int idx = 0; langs && langs[idx]; idx++) { + if (lang && strcmp(langs[idx], lang) == 0) + return INT_MAX - idx; + } + return 0; +} + +/* Get the track wanted by the user. + * tid is the track ID requested by the user (-2: deselect, -1: default) + * lang is a string list, NULL is same as empty list + * Sort tracks based on the following criteria, and pick the first: + * 0) track matches tid (always wins) + * 1) track is external + * 1b) track was passed explicitly (is not an auto-loaded subtitle) + * 2) earlier match in lang list + * 3) track is marked default + * 4) lower track number + * If select_fallback is not set, 4) is only used to determine whether a + * matching track is preferred over another track. Otherwise, always pick a + * track (if nothing else matches, return the track with lowest ID). + */ +// Return whether t1 is preferred over t2 +static bool compare_track(struct track *t1, struct track *t2, char **langs) +{ + if (t1->is_external != t2->is_external) + return t1->is_external; + if (t1->auto_loaded != t2->auto_loaded) + return !t1->auto_loaded; + int l1 = match_lang(langs, t1->lang), l2 = match_lang(langs, t2->lang); + if (l1 != l2) + return l1 > l2; + if (t1->default_track != t2->default_track) + return t1->default_track; + if (t1->attached_picture != t2->attached_picture) + return !t1->attached_picture; + return t1->user_tid <= t2->user_tid; +} +static struct track *select_track(struct MPContext *mpctx, + enum stream_type type, int tid, char **langs) +{ + if (tid == -2) + return NULL; + bool select_fallback = type == STREAM_VIDEO || type == STREAM_AUDIO; + struct track *pick = NULL; + for (int n = 0; n < mpctx->num_tracks; n++) { + struct track *track = mpctx->tracks[n]; + if (track->type != type) + continue; + if (track->user_tid == tid) + return track; + if (!pick || compare_track(track, pick, langs)) + pick = track; + } + if (pick && !select_fallback && !pick->is_external + && !match_lang(langs, pick->lang) && !pick->default_track) + pick = NULL; + if (pick && pick->attached_picture && !mpctx->opts->audio_display) + pick = NULL; + return pick; +} + +static char *track_layout_hash(struct MPContext *mpctx) +{ + char *h = talloc_strdup(NULL, ""); + for (int type = 0; type < STREAM_TYPE_COUNT; type++) { + for (int n = 0; n < mpctx->num_tracks; n++) { + struct track *track = mpctx->tracks[n]; + if (track->type != type) + continue; + h = talloc_asprintf_append_buffer(h, "%d-%d-%d-%d-%s\n", type, + track->user_tid, track->default_track, track->is_external, + track->lang ? track->lang : ""); + } + } + return h; +} + +// Normally, video/audio/sub track selection is persistent across files. This +// code resets track selection if the new file has a different track layout. +static void check_previous_track_selection(struct MPContext *mpctx) +{ + struct MPOpts *opts = mpctx->opts; + + if (!mpctx->track_layout_hash) + return; + + char *h = track_layout_hash(mpctx); + if (strcmp(h, mpctx->track_layout_hash) != 0) { + // Reset selection, but only if they're not "auto" or "off". + if (opts->video_id >= 0) + mpctx->opts->video_id = -1; + if (opts->audio_id >= 0) + mpctx->opts->audio_id = -1; + if (opts->sub_id >= 0) + mpctx->opts->sub_id = -1; + talloc_free(mpctx->track_layout_hash); + mpctx->track_layout_hash = NULL; + } + talloc_free(h); +} + +void mp_switch_track(struct MPContext *mpctx, enum stream_type type, + struct track *track) +{ + assert(!track || track->type == type); + + struct track *current = mpctx->current_track[type]; + if (track == current) + return; + + if (type == STREAM_VIDEO) { + int uninit = INITIALIZED_VCODEC; + if (!mpctx->opts->force_vo) + uninit |= mpctx->opts->fixed_vo && track ? 0 : INITIALIZED_VO; + uninit_player(mpctx, uninit); + } else if (type == STREAM_AUDIO) { + uninit_player(mpctx, INITIALIZED_AO | INITIALIZED_ACODEC); + } else if (type == STREAM_SUB) { + uninit_player(mpctx, INITIALIZED_SUB); + } + + mpctx->current_track[type] = track; + + int user_tid = track ? track->user_tid : -2; + if (type == STREAM_VIDEO) { + mpctx->opts->video_id = user_tid; + reinit_video_chain(mpctx); + mp_notify_property(mpctx, "vid"); + } else if (type == STREAM_AUDIO) { + mpctx->opts->audio_id = user_tid; + reinit_audio_chain(mpctx); + mp_notify_property(mpctx, "aid"); + } else if (type == STREAM_SUB) { + mpctx->opts->sub_id = user_tid; + reinit_subs(mpctx); + mp_notify_property(mpctx, "sid"); + } + + talloc_free(mpctx->track_layout_hash); + mpctx->track_layout_hash = talloc_steal(mpctx, track_layout_hash(mpctx)); +} + +struct track *mp_track_by_tid(struct MPContext *mpctx, enum stream_type type, + int tid) +{ + if (tid == -1) + return mpctx->current_track[type]; + for (int n = 0; n < mpctx->num_tracks; n++) { + struct track *track = mpctx->tracks[n]; + if (track->type == type && track->user_tid == tid) + return track; + } + return NULL; +} + +bool mp_remove_track(struct MPContext *mpctx, struct track *track) +{ + if (track->under_timeline) + return false; + if (!track->is_external) + return false; + + if (mpctx->current_track[track->type] == track) { + mp_switch_track(mpctx, track->type, NULL); + if (mpctx->current_track[track->type] == track) + return false; + } + + int index = 0; + while (index < mpctx->num_tracks && mpctx->tracks[index] != track) + index++; + assert(index < mpctx->num_tracks); + while (index + 1 < mpctx->num_tracks) { + mpctx->tracks[index] = mpctx->tracks[index + 1]; + index++; + } + mpctx->num_tracks--; + talloc_free(track); + + mp_notify(mpctx, MP_EVENT_TRACKS_CHANGED, NULL); + + return true; +} + +static void open_subtitles_from_options(struct MPContext *mpctx) +{ + if (mpctx->opts->sub_name) { + for (int i = 0; mpctx->opts->sub_name[i] != NULL; ++i) + mp_add_subtitles(mpctx, mpctx->opts->sub_name[i]); + } + if (mpctx->opts->sub_auto) { // auto load sub file ... + void *tmp = talloc_new(NULL); + char *base_filename = mpctx->filename; + char *stream_filename = NULL; + if (stream_control(mpctx->stream, STREAM_CTRL_GET_BASE_FILENAME, + &stream_filename) > 0) + base_filename = talloc_steal(tmp, stream_filename); + struct subfn *list = find_text_subtitles(mpctx->opts, base_filename); + talloc_steal(tmp, list); + for (int i = 0; list && list[i].fname; i++) { + char *filename = list[i].fname; + char *lang = list[i].lang; + for (int n = 0; n < mpctx->num_sources; n++) { + if (strcmp(mpctx->sources[n]->stream->url, filename) == 0) + goto skip; + } + struct track *track = mp_add_subtitles(mpctx, filename); + if (track) { + track->auto_loaded = true; + if (!track->lang) + track->lang = talloc_strdup(track, lang); + } + skip:; + } + talloc_free(tmp); + } +} + +static struct track *open_external_file(struct MPContext *mpctx, char *filename, + char *demuxer_name, int stream_cache, + enum stream_type filter) +{ + struct MPOpts *opts = mpctx->opts; + if (!filename) + return NULL; + char *disp_filename = filename; + if (strncmp(disp_filename, "memory://", 9) == 0) + disp_filename = "memory://"; // avoid noise + struct stream *stream = stream_open(filename, mpctx->opts); + if (!stream) + goto err_out; + stream_enable_cache_percent(&stream, stream_cache, + opts->stream_cache_def_size, + opts->stream_cache_min_percent, + opts->stream_cache_seek_min_percent); + struct demuxer_params params = { + .ass_library = mpctx->ass_library, // demux_libass requires it + }; + struct demuxer *demuxer = + demux_open(stream, demuxer_name, ¶ms, mpctx->opts); + if (!demuxer) { + free_stream(stream); + goto err_out; + } + struct track *first = NULL; + for (int n = 0; n < demuxer->num_streams; n++) { + struct sh_stream *sh = demuxer->streams[n]; + if (sh->type == filter) { + struct track *t = add_stream_track(mpctx, sh, false); + t->is_external = true; + t->title = talloc_strdup(t, disp_filename); + t->external_filename = talloc_strdup(t, filename); + first = t; + } + } + if (!first) { + free_demuxer(demuxer); + free_stream(stream); + MP_WARN(mpctx, "No streams added from file %s.\n", + disp_filename); + goto err_out; + } + MP_TARRAY_APPEND(NULL, mpctx->sources, mpctx->num_sources, demuxer); + return first; + +err_out: + MP_ERR(mpctx, "Can not open external file %s.\n", + disp_filename); + return false; +} + +static void open_audiofiles_from_options(struct MPContext *mpctx) +{ + struct MPOpts *opts = mpctx->opts; + open_external_file(mpctx, opts->audio_stream, opts->audio_demuxer_name, + opts->audio_stream_cache, STREAM_AUDIO); +} + +struct track *mp_add_subtitles(struct MPContext *mpctx, char *filename) +{ + struct MPOpts *opts = mpctx->opts; + return open_external_file(mpctx, filename, opts->sub_demuxer_name, 0, + STREAM_SUB); +} + +static void open_subtitles_from_resolve(struct MPContext *mpctx) +{ + struct MPOpts *opts = mpctx->opts; + struct mp_resolve_result *res = mpctx->resolve_result; + if (!res) + return; + for (int n = 0; n < res->num_subs; n++) { + struct mp_resolve_sub *sub = res->subs[n]; + char *s = talloc_strdup(NULL, sub->url); + if (!s) + s = talloc_asprintf(NULL, "memory://%s", sub->data); + struct track *t = + open_external_file(mpctx, s, opts->sub_demuxer_name, 0, STREAM_SUB); + talloc_free(s); + if (t) + t->lang = talloc_strdup(t, sub->lang); + } +} + +static bool attachment_is_font(struct demux_attachment *att) +{ + if (!att->name || !att->type || !att->data || !att->data_size) + return false; + // match against MIME types + if (strcmp(att->type, "application/x-truetype-font") == 0 + || strcmp(att->type, "application/x-font") == 0) + return true; + // fallback: match against file extension + if (strlen(att->name) > 4) { + char *ext = att->name + strlen(att->name) - 4; + if (strcasecmp(ext, ".ttf") == 0 || strcasecmp(ext, ".ttc") == 0 + || strcasecmp(ext, ".otf") == 0) + return true; + } + return false; +} + +static void add_subtitle_fonts_from_sources(struct MPContext *mpctx) +{ +#if HAVE_LIBASS + if (mpctx->opts->ass_enabled) { + for (int j = 0; j < mpctx->num_sources; j++) { + struct demuxer *d = mpctx->sources[j]; + for (int i = 0; i < d->num_attachments; i++) { + struct demux_attachment *att = d->attachments + i; + if (mpctx->opts->use_embedded_fonts && attachment_is_font(att)) + ass_add_font(mpctx->ass_library, att->name, att->data, + att->data_size); + } + } + } +#endif +} + +static void init_sub_renderer(struct MPContext *mpctx) +{ +#if HAVE_LIBASS + assert(!(mpctx->initialized_flags & INITIALIZED_LIBASS)); + assert(!mpctx->ass_renderer); + + mpctx->ass_renderer = ass_renderer_init(mpctx->ass_library); + if (mpctx->ass_renderer) + mp_ass_configure_fonts(mpctx->ass_renderer, mpctx->opts->sub_text_style); + mpctx->initialized_flags |= INITIALIZED_LIBASS; +#endif +} + +static struct mp_resolve_result *resolve_url(const char *filename, + struct MPOpts *opts) +{ + if (!mp_is_url(bstr0(filename))) + return NULL; +#if HAVE_LIBQUVI + return mp_resolve_quvi(filename, opts); +#else + return NULL; +#endif +} + +static void print_resolve_contents(struct mp_log *log, + struct mp_resolve_result *res) +{ + mp_msg_log(log, MSGL_V, "Resolve:\n"); + mp_msg_log(log, MSGL_V, " title: %s\n", res->title); + mp_msg_log(log, MSGL_V, " url: %s\n", res->url); + for (int n = 0; n < res->num_srcs; n++) { + mp_msg_log(log, MSGL_V, " source %d:\n", n); + if (res->srcs[n]->url) + mp_msg_log(log, MSGL_V, " url: %s\n", res->srcs[n]->url); + if (res->srcs[n]->encid) + mp_msg_log(log, MSGL_V, " encid: %s\n", res->srcs[n]->encid); + } + for (int n = 0; n < res->num_subs; n++) { + mp_msg_log(log, MSGL_V, " subtitle %d:\n", n); + if (res->subs[n]->url) + mp_msg_log(log, MSGL_V, " url: %s\n", res->subs[n]->url); + if (res->subs[n]->lang) + mp_msg_log(log, MSGL_V, " lang: %s\n", res->subs[n]->lang); + if (res->subs[n]->data) { + mp_msg_log(log, MSGL_V, " data: %zd bytes\n", + strlen(res->subs[n]->data)); + } + } + if (res->playlist) { + mp_msg_log(log, MSGL_V, " playlist with %d entries\n", + playlist_entry_count(res->playlist)); + } +} + +// Replace the current playlist entry with playlist contents. Moves the entries +// from the given playlist pl, so the entries don't actually need to be copied. +static void transfer_playlist(struct MPContext *mpctx, struct playlist *pl) +{ + if (pl->first) { + playlist_transfer_entries(mpctx->playlist, pl); + // current entry is replaced + if (mpctx->playlist->current) + playlist_remove(mpctx->playlist, mpctx->playlist->current); + } else { + MP_WARN(mpctx, "Empty playlist!\n"); + } +} + +static void print_timeline(struct MPContext *mpctx) +{ + if (mpctx->timeline) { + int part_count = mpctx->num_timeline_parts; + MP_VERBOSE(mpctx, "Timeline contains %d parts from %d " + "sources. Total length %.3f seconds.\n", part_count, + mpctx->num_sources, mpctx->timeline[part_count].start); + MP_VERBOSE(mpctx, "Source files:\n"); + for (int i = 0; i < mpctx->num_sources; i++) + MP_VERBOSE(mpctx, "%d: %s\n", i, + mpctx->sources[i]->filename); + MP_VERBOSE(mpctx, "Timeline parts: (number, start, " + "source_start, source):\n"); + for (int i = 0; i < part_count; i++) { + struct timeline_part *p = mpctx->timeline + i; + MP_VERBOSE(mpctx, "%3d %9.3f %9.3f %p/%s\n", i, p->start, + p->source_start, p->source, p->source->filename); + } + MP_VERBOSE(mpctx, "END %9.3f\n", + mpctx->timeline[part_count].start); + } +} + +/* When demux performs a blocking operation (network connection or + * cache filling) if the operation fails we use this function to check + * if it was interrupted by the user. + * The function returns whether it was interrupted. */ +static bool demux_was_interrupted(struct MPContext *mpctx) +{ + for (;;) { + if (mpctx->stop_play != KEEP_PLAYING + |