summaryrefslogtreecommitdiffstats
path: root/player/loadfile.c
diff options
context:
space:
mode:
Diffstat (limited to 'player/loadfile.c')
-rw-r--r--player/loadfile.c1423
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, &params, 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
+ && mpctx->stop_play != AT_END_OF_FILE)
+ return true;
+ mp_cmd_t *cmd = mp_input_get_cmd(mpctx->input, 0, 0);
+ if (!cmd)
+ break;
+ if (mp_input_is_abort_cmd(cmd->id))
+ run_command(mpctx, cmd);
+ mp_cmd_free(cmd);
+ }
+ return false;
+}
+
+static void load_per_file_options(m_config_t *conf,
+ struct playlist_param *params,
+ int params_count)
+{
+ for (int n = 0; n < params_count; n++) {
+ m_config_set_option_ext(conf, params[n].name, params[n].value,
+ M_SETOPT_BACKUP);
+ }
+}
+
+// Start playing the current playlist entry.
+// Handle initialization and deinitialization.
+static void play_current_file(struct MPContext *mpctx)
+{
+ struct MPOpts *opts = mpctx->opts;
+ double playback_start = -1e100;
+
+ mpctx->initialized_flags |= INITIALIZED_PLAYBACK;
+
+ mp_notify(mpctx, MP_EVENT_START_FILE, NULL);
+ mp_flush_events(mpctx);
+
+ mpctx->stop_play = 0;
+ mpctx->filename = NULL