Something went wrong. Try again.
🎥 Command line media player
Something went wrong. Try again.
C
12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634163516361637163816391640164116421643164416451646164716481649165016511652165316541655165616571658165916601661166216631664166516661667166816691670167116721673167416751676167716781679168016811682168316841685168616871688168916901691169216931694169516961697169816991700170117021703170417051706170717081709171017111712171317141715171617171718171917201721172217231724172517261727172817291730173117321733173417351736173717381739174017411742174317441745174617471748174917501751175217531754175517561757175817591760176117621763176417651766176717681769177017711772177317741775177617771778177917801781178217831784178517861787178817891790179117921793179417951796179717981799180018011802180318041805180618071808180918101811181218131814181518161817181818191820182118221823182418251826182718281829183018311832183318341835183618371838183918401841184218431844184518461847184818491850185118521853185418551856185718581859186018611862186318641865186618671868186918701871187218731874187518761877187818791880188118821883188418851886188718881889189018911892189318941895189618971898189919001901190219031904190519061907190819091910/* * This file is part of mpv. * * mpv is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * mpv 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 Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with mpv. If not, see <http://www.gnu.org/licenses/>. */
#include <stddef.h>#include <stdbool.h>#include <strings.h>#include <inttypes.h>#include <assert.h>
#include <libavutil/avutil.h>
#include "config.h"#include "mpv_talloc.h"
#include "misc/thread_pool.h"#include "misc/thread_tools.h"#include "osdep/io.h"#include "osdep/terminal.h"#include "osdep/threads.h"#include "osdep/timer.h"
#include "client.h"#include "common/msg.h"#include "common/msg_control.h"#include "common/global.h"#include "options/path.h"#include "options/m_config.h"#include "options/parse_configfile.h"#include "common/playlist.h"#include "options/options.h"#include "options/m_property.h"#include "common/common.h"#include "common/encode.h"#include "common/recorder.h"#include "input/input.h"
#include "audio/out/ao.h"#include "filters/f_decoder_wrapper.h"#include "filters/f_lavfi.h"#include "filters/filter_internal.h"#include "demux/demux.h"#include "stream/stream.h"#include "sub/dec_sub.h"#include "external_files.h"#include "video/out/vo.h"
#include "core.h"#include "command.h"#include "libmpv/client.h"
// Called from the demuxer thread if a new packet is available, or other changes.static void wakeup_demux(void *pctx){ struct MPContext *mpctx = pctx; mp_wakeup_core(mpctx);}
// Called by foreign threads when playback should be stopped and such.void mp_abort_playback_async(struct MPContext *mpctx){ mp_cancel_trigger(mpctx->playback_abort);
pthread_mutex_lock(&mpctx->abort_lock);
for (int n = 0; n < mpctx->num_abort_list; n++) { struct mp_abort_entry *abort = mpctx->abort_list[n]; if (abort->coupled_to_playback) mp_abort_trigger_locked(mpctx, abort); }
pthread_mutex_unlock(&mpctx->abort_lock);}
// Add it to the global list, and allocate required data structures.void mp_abort_add(struct MPContext *mpctx, struct mp_abort_entry *abort){ pthread_mutex_lock(&mpctx->abort_lock); assert(!abort->cancel); abort->cancel = mp_cancel_new(NULL); MP_TARRAY_APPEND(NULL, mpctx->abort_list, mpctx->num_abort_list, abort); mp_abort_recheck_locked(mpctx, abort); pthread_mutex_unlock(&mpctx->abort_lock);}
// Remove Add it to the global list, and free/clear required data structures.// Does not deallocate the abort value itself.void mp_abort_remove(struct MPContext *mpctx, struct mp_abort_entry *abort){ pthread_mutex_lock(&mpctx->abort_lock); for (int n = 0; n < mpctx->num_abort_list; n++) { if (mpctx->abort_list[n] == abort) { MP_TARRAY_REMOVE_AT(mpctx->abort_list, mpctx->num_abort_list, n); TA_FREEP(&abort->cancel); abort = NULL; // it's not free'd, just clear for the assert below break; } } assert(!abort); // should have been in the list pthread_mutex_unlock(&mpctx->abort_lock);}
// Verify whether the abort needs to be signaled after changing certain fields// in abort.void mp_abort_recheck_locked(struct MPContext *mpctx, struct mp_abort_entry *abort){ if ((abort->coupled_to_playback && mp_cancel_test(mpctx->playback_abort)) || mpctx->abort_all) { mp_abort_trigger_locked(mpctx, abort); }}
void mp_abort_trigger_locked(struct MPContext *mpctx, struct mp_abort_entry *abort){ mp_cancel_trigger(abort->cancel);}
static void kill_demuxers_reentrant(struct MPContext *mpctx, struct demuxer **demuxers, int num_demuxers){ struct demux_free_async_state **items = NULL; int num_items = 0;
for (int n = 0; n < num_demuxers; n++) { struct demuxer *d = demuxers[n];
if (!demux_cancel_test(d)) { // Make sure it is set if it wasn't yet. demux_set_wakeup_cb(d, wakeup_demux, mpctx);
struct demux_free_async_state *item = demux_free_async(d); if (item) { MP_TARRAY_APPEND(NULL, items, num_items, item); d = NULL; } }
demux_cancel_and_free(d); }
if (!num_items) return;
MP_DBG(mpctx, "Terminating demuxers...\n");
double end = mp_time_sec() + mpctx->opts->demux_termination_timeout; bool force = false; while (num_items) { double wait = end - mp_time_sec();
for (int n = 0; n < num_items; n++) { struct demux_free_async_state *item = items[n]; if (demux_free_async_finish(item)) { items[n] = items[num_items - 1]; num_items -= 1; n--; goto repeat; } else if (wait < 0) { demux_free_async_force(item); if (!force) MP_VERBOSE(mpctx, "Forcefully terminating demuxers...\n"); force = true; } }
if (wait >= 0) mp_set_timeout(mpctx, wait); mp_idle(mpctx); repeat:; }
talloc_free(items);
MP_DBG(mpctx, "Done terminating demuxers.\n");}
static void uninit_demuxer(struct MPContext *mpctx){ for (int r = 0; r < NUM_PTRACKS; r++) { for (int t = 0; t < STREAM_TYPE_COUNT; t++) mpctx->current_track[r][t] = NULL; } mpctx->seek_slave = NULL;
talloc_free(mpctx->chapters); mpctx->chapters = NULL; mpctx->num_chapters = 0;
mp_abort_cache_dumping(mpctx);
struct demuxer **demuxers = NULL; int num_demuxers = 0;
if (mpctx->demuxer) MP_TARRAY_APPEND(NULL, demuxers, num_demuxers, mpctx->demuxer); mpctx->demuxer = NULL;
for (int i = 0; i < mpctx->num_tracks; i++) { struct track *track = mpctx->tracks[i];
assert(!track->dec && !track->d_sub); assert(!track->vo_c && !track->ao_c); assert(!track->sink); assert(!track->remux_sink);
// Demuxers can be added in any order (if they appear mid-stream), and // we can't know which tracks uses which, so here's some O(n^2) trash. for (int n = 0; n < num_demuxers; n++) { if (demuxers[n] == track->demuxer) { track->demuxer = NULL; break; } } if (track->demuxer) MP_TARRAY_APPEND(NULL, demuxers, num_demuxers, track->demuxer);
talloc_free(track); } mpctx->num_tracks = 0;
kill_demuxers_reentrant(mpctx, demuxers, num_demuxers); talloc_free(demuxers);}
#define APPEND(s, ...) mp_snprintf_cat(s, sizeof(s), __VA_ARGS__)
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 = "?"; switch (t->type) { case STREAM_VIDEO: tname = "Video"; selopt = "vid"; langopt = NULL; break; case STREAM_AUDIO: tname = "Audio"; selopt = "aid"; langopt = "alang"; break; case STREAM_SUB: tname = "Subs"; selopt = "sid"; langopt = "slang"; break; } char b[2048] = {0}; APPEND(b, " %3s %-5s", t->selected ? "(+)" : "", tname); APPEND(b, " --%s=%d", selopt, t->user_tid); if (t->lang && langopt) APPEND(b, " --%s=%s", langopt, t->lang); if (t->default_track) APPEND(b, " (*)"); if (t->forced_track) APPEND(b, " (f)"); if (t->attached_picture) APPEND(b, " [P]"); if (t->title) APPEND(b, " '%s'", t->title); const char *codec = s ? s->codec->codec : NULL; APPEND(b, " (%s", codec ? codec : "<unknown>"); if (t->type == STREAM_VIDEO) { if (s && s->codec->disp_w) APPEND(b, " %dx%d", s->codec->disp_w, s->codec->disp_h); if (s && s->codec->fps) APPEND(b, " %.3ffps", s->codec->fps); } else if (t->type == STREAM_AUDIO) { if (s && s->codec->channels.num) APPEND(b, " %dch", s->codec->channels.num); if (s && s->codec->samplerate) APPEND(b, " %dHz", s->codec->samplerate); } APPEND(b, ")"); if (t->is_external) APPEND(b, " (external)"); MP_INFO(mpctx, "%s\n", b);}
void print_track_list(struct MPContext *mpctx, const char *msg){ if (msg) MP_INFO(mpctx, "%s\n", msg); 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]); }}
void update_demuxer_properties(struct MPContext *mpctx){ struct demuxer *demuxer = mpctx->demuxer; if (!demuxer) return; demux_update(demuxer, get_current_time(mpctx)); int events = demuxer->events; if ((events & DEMUX_EVENT_INIT) && demuxer->num_editions > 1) { for (int n = 0; n < demuxer->num_editions; n++) { struct demux_edition *edition = &demuxer->editions[n]; char b[128] = {0}; APPEND(b, " %3s --edition=%d", n == demuxer->edition ? "(+)" : "", n); char *name = mp_tags_get_str(edition->metadata, "title"); if (name) APPEND(b, " '%s'", name); if (edition->default_edition) APPEND(b, " (*)"); MP_INFO(mpctx, "%s\n", b); } } struct demuxer *tracks = mpctx->demuxer; if (tracks->events & DEMUX_EVENT_STREAMS) { add_demuxer_tracks(mpctx, tracks); print_track_list(mpctx, NULL); tracks->events &= ~DEMUX_EVENT_STREAMS; } if (events & DEMUX_EVENT_METADATA) { struct mp_tags *info = mp_tags_filtered(mpctx, demuxer->metadata, mpctx->opts->display_tags); // prev is used to attempt to print changed tags only (to some degree) struct mp_tags *prev = mpctx->filtered_tags; int n_prev = 0; bool had_output = false; for (int n = 0; n < info->num_keys; n++) { if (prev && n_prev < prev->num_keys) { if (strcmp(prev->keys[n_prev], info->keys[n]) == 0) { n_prev++; if (strcmp(prev->values[n_prev - 1], info->values[n]) == 0) continue; } } struct mp_log *log = mp_log_new(NULL, mpctx->log, "!display-tags"); if (!had_output) mp_info(log, "File tags:\n"); mp_info(log, " %s: %s\n", info->keys[n], info->values[n]); had_output = true; talloc_free(log); } talloc_free(mpctx->filtered_tags); mpctx->filtered_tags = info; mp_notify(mpctx, MPV_EVENT_METADATA_UPDATE, NULL); } if (events & DEMUX_EVENT_DURATION) mp_notify(mpctx, MP_EVENT_DURATION_UPDATE, NULL); demuxer->events = 0;}
// Enables or disables the stream for the given track, according to// track->selected.void reselect_demux_stream(struct MPContext *mpctx, struct track *track){ if (!track->stream) return; double pts = get_current_time(mpctx); if (pts != MP_NOPTS_VALUE) { pts += get_track_seek_offset(mpctx, track); if (track->type == STREAM_SUB) pts -= 10.0; } demuxer_select_track(track->demuxer, track->stream, pts, track->selected); if (track == mpctx->seek_slave) mpctx->seek_slave = NULL;}
static void enable_demux_thread(struct MPContext *mpctx, struct demuxer *demux){ if (mpctx->opts->demuxer_thread && !demux->fully_read) { demux_set_wakeup_cb(demux, wakeup_demux, mpctx); demux_start_thread(demux); }}
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 demuxer *demuxer, struct sh_stream *stream){ for (int i = 0; i < mpctx->num_tracks; i++) { struct track *track = mpctx->tracks[i]; if (track->stream == stream) 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, .ff_index = stream->ff_index, .title = stream->title, .default_track = stream->default_track, .forced_track = stream->forced_track, .dependent_track = stream->dependent_track, .visual_impaired_track = stream->visual_impaired_track, .hearing_impaired_track = stream->hearing_impaired_track, .attached_picture = stream->attached_picture != NULL, .lang = stream->lang, .demuxer = demuxer, .stream = stream, }; MP_TARRAY_APPEND(mpctx, mpctx->tracks, mpctx->num_tracks, track);
mp_notify(mpctx, MPV_EVENT_TRACKS_CHANGED, NULL);
return track;}
void add_demuxer_tracks(struct MPContext *mpctx, struct demuxer *demuxer){ for (int n = 0; n < demux_get_num_stream(demuxer); n++) add_stream_track(mpctx, demuxer, demux_get_stream(demuxer, n));}
// 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 && strcasecmp(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: * 0a) track matches ff-index (always wins) * 0b) track matches tid (almost always wins) * 0c) track is not from --external-file * 1) track is external (no_default cancels this) * 1b) track was passed explicitly (is not an auto-loaded subtitle) * 2) earlier match in lang list * 3a) track is marked forced * 3b) track is marked default * 4) attached picture, HLS bitrate * 5) lower track number * If select_fallback is not set, 5) 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 t2static bool compare_track(struct track *t1, struct track *t2, char **langs, struct MPOpts *opts){ if (!opts->autoload_files && t1->is_external != t2->is_external) return !t1->is_external; bool ext1 = t1->is_external && !t1->no_default; bool ext2 = t2->is_external && !t2->no_default; if (ext1 != ext2) return ext1; 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->forced_track != t2->forced_track) return t1->forced_track; if (t1->default_track != t2->default_track) return t1->default_track; if (t1->attached_picture != t2->attached_picture) return !t1->attached_picture; if (t1->stream && t2->stream && opts->hls_bitrate >= 0 && t1->stream->hls_bitrate != t2->stream->hls_bitrate) { bool t1_ok = t1->stream->hls_bitrate <= opts->hls_bitrate; bool t2_ok = t2->stream->hls_bitrate <= opts->hls_bitrate; if (t1_ok != t2_ok) return t1_ok; if (t1_ok && t2_ok) return t1->stream->hls_bitrate > t2->stream->hls_bitrate; return t1->stream->hls_bitrate < t2->stream->hls_bitrate; } return t1->user_tid <= t2->user_tid;}
static bool duplicate_track(struct MPContext *mpctx, int order, enum stream_type type, struct track *track){ for (int i = 0; i < order; i++) { if (mpctx->current_track[i][type] == track) return true; } return false;}
struct track *select_default_track(struct MPContext *mpctx, int order, enum stream_type type){ struct MPOpts *opts = mpctx->opts; int tid = opts->stream_id[order][type]; char **langs = opts->stream_lang[type]; 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 (track->no_auto_select) continue; if (duplicate_track(mpctx, order, type, track)) continue; if (!pick || compare_track(track, pick, langs, mpctx->opts)) pick = track; } if (pick && !select_fallback && !(pick->is_external && !pick->no_default) && !match_lang(langs, pick->lang) && !pick->default_track && !pick->forced_track) pick = NULL; if (pick && pick->attached_picture && !mpctx->opts->audio_display) pick = NULL; if (pick && !opts->autoload_files && pick->is_external) 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". The // defaults are -1 (default selection), or -2 (off) for secondary tracks. for (int t = 0; t < STREAM_TYPE_COUNT; t++) { for (int i = 0; i < NUM_PTRACKS; i++) { if (opts->stream_id[i][t] >= 0) { opts->stream_id[i][t] = i == 0 ? -1 : -2; m_config_notify_change_opt_ptr(mpctx->mconfig, &opts->stream_id[i][t]); } } } talloc_free(mpctx->track_layout_hash); mpctx->track_layout_hash = NULL; } talloc_free(h);}
void mp_switch_track_n(struct MPContext *mpctx, int order, enum stream_type type, struct track *track, int flags){ assert(!track || track->type == type); assert(order >= 0 && order < NUM_PTRACKS);
// Mark the current track selection as explicitly user-requested. (This is // different from auto-selection or disabling a track due to errors.) if (flags & FLAG_MARK_SELECTION) { mpctx->opts->stream_id[order][type] = track ? track->user_tid : -2; m_config_notify_change_opt_ptr(mpctx->mconfig, &mpctx->opts->stream_id[order][type]); }
// No decoder should be initialized yet. if (!mpctx->demuxer) return;
struct track *current = mpctx->current_track[order][type]; if (track == current) return;
if (current && current->sink) { MP_ERR(mpctx, "Can't disable input to complex filter.\n"); return; } if ((type == STREAM_VIDEO && mpctx->vo_chain && !mpctx->vo_chain->track) || (type == STREAM_AUDIO && mpctx->ao_chain && !mpctx->ao_chain->track)) { MP_ERR(mpctx, "Can't switch away from complex filter output.\n"); return; }
if (track && track->selected) { // Track has been selected in a different order parameter. MP_ERR(mpctx, "Track %d is already selected.\n", track->user_tid); return; }
if (order == 0) { if (type == STREAM_VIDEO) { uninit_video_chain(mpctx); if (!track) handle_force_window(mpctx, true); } else if (type == STREAM_AUDIO) { clear_audio_output_buffers(mpctx); uninit_audio_chain(mpctx); uninit_audio_out(mpctx); } } if (type == STREAM_SUB) uninit_sub(mpctx, current);
if (current) { if (current->remux_sink) close_recorder_and_error(mpctx); current->selected = false; reselect_demux_stream(mpctx, current); }
mpctx->current_track[order][type] = track;
if (track) { track->selected = true; reselect_demux_stream(mpctx, track); }
if (type == STREAM_VIDEO && order == 0) { reinit_video_chain(mpctx); } else if (type == STREAM_AUDIO && order == 0) { reinit_audio_chain(mpctx); } else if (type == STREAM_SUB && order >= 0 && order <= 2) { reinit_sub(mpctx, track); }
mp_notify(mpctx, MPV_EVENT_TRACK_SWITCHED, NULL); mp_wakeup_core(mpctx);
talloc_free(mpctx->track_layout_hash); mpctx->track_layout_hash = talloc_steal(mpctx, track_layout_hash(mpctx));}
void mp_switch_track(struct MPContext *mpctx, enum stream_type type, struct track *track, int flags){ mp_switch_track_n(mpctx, 0, type, track, flags);}
void mp_deselect_track(struct MPContext *mpctx, struct track *track){ if (track && track->selected) { for (int t = 0; t < NUM_PTRACKS; t++) mp_switch_track_n(mpctx, t, track->type, NULL, 0); }}
struct track *mp_track_by_tid(struct MPContext *mpctx, enum stream_type type, int tid){ if (tid == -1) return mpctx->current_track[0][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->is_external) return false;
mp_deselect_track(mpctx, track); if (track->selected) return false;
struct demuxer *d = track->demuxer;
if (mpctx->seek_slave == track) mpctx->seek_slave = NULL;
int index = 0; while (index < mpctx->num_tracks && mpctx->tracks[index] != track) index++; MP_TARRAY_REMOVE_AT(mpctx->tracks, mpctx->num_tracks, index); talloc_free(track);
// Close the demuxer, unless there is still a track using it. These are // all external tracks. bool in_use = false; for (int n = mpctx->num_tracks - 1; n >= 0 && !in_use; n--) in_use |= mpctx->tracks[n]->demuxer == d;
if (!in_use) demux_cancel_and_free(d);
mp_notify(mpctx, MPV_EVENT_TRACKS_CHANGED, NULL);
return true;}
// Add the given file as additional track. The filter argument controls how or// if tracks are auto-selected at any point.// To be run on a worker thread, locked (temporarily unlocks core).// cancel will generally be used to abort the loading process, but on success// the demuxer is changed to be slaved to mpctx->playback_abort instead.int mp_add_external_file(struct MPContext *mpctx, char *filename, enum stream_type filter, struct mp_cancel *cancel){ struct MPOpts *opts = mpctx->opts; if (!filename || mp_cancel_test(cancel)) return -1;
char *disp_filename = filename; if (strncmp(disp_filename, "memory://", 9) == 0) disp_filename = "memory://"; // avoid noise
struct demuxer_params params = { .is_top_level = true, .stream_flags = STREAM_ORIGIN_DIRECT, };
switch (filter) { case STREAM_SUB: params.force_format = opts->sub_demuxer_name; break; case STREAM_AUDIO: params.force_format = opts->audio_demuxer_name; break; }
mp_core_unlock(mpctx);
struct demuxer *demuxer = demux_open_url(filename, ¶ms, cancel, mpctx->global); if (demuxer) enable_demux_thread(mpctx, demuxer);
mp_core_lock(mpctx);
// The command could have overlapped with playback exiting. (We don't care // if playback has started again meanwhile - weird, but not a problem.) if (mpctx->stop_play) goto err_out;
if (!demuxer) goto err_out;
if (filter != STREAM_SUB && opts->rebase_start_time) demux_set_ts_offset(demuxer, -demuxer->start_time);
bool has_any = false; for (int n = 0; n < demux_get_num_stream(demuxer); n++) { struct sh_stream *sh = demux_get_stream(demuxer, n); if (sh->type == filter || filter == STREAM_TYPE_COUNT) { has_any = true; break; } }
if (!has_any) { char *tname = mp_tprintf(20, "%s ", stream_type_name(filter)); if (filter == STREAM_TYPE_COUNT) tname = ""; MP_ERR(mpctx, "No %sstreams in file %s.\n", tname, disp_filename); goto err_out; }
int first_num = -1; for (int n = 0; n < demux_get_num_stream(demuxer); n++) { struct sh_stream *sh = demux_get_stream(demuxer, n); struct track *t = add_stream_track(mpctx, demuxer, sh); t->is_external = true; if (sh->title && sh->title[0]) { t->title = talloc_strdup(t, sh->title); } else { t->title = talloc_strdup(t, mp_basename(disp_filename)); } t->external_filename = talloc_strdup(t, filename); t->no_default = sh->type != filter; t->no_auto_select = t->no_default; if (first_num < 0 && (filter == STREAM_TYPE_COUNT || sh->type == filter)) first_num = mpctx->num_tracks - 1; }
mp_cancel_set_parent(demuxer->cancel, mpctx->playback_abort);
return first_num;
err_out: demux_cancel_and_free(demuxer); if (!mp_cancel_test(cancel)) MP_ERR(mpctx, "Can not open external file %s.\n", disp_filename); return -1;}
// to be run on a worker thread, locked (temporarily unlocks core)static void open_external_files(struct MPContext *mpctx, char **files, enum stream_type filter){ // Need a copy, because the option value could be mutated during iteration. void *tmp = talloc_new(NULL); files = mp_dup_str_array(tmp, files);
for (int n = 0; files && files[n]; n++) mp_add_external_file(mpctx, files[n], filter, mpctx->playback_abort);
talloc_free(tmp);}
// See mp_add_external_file() for meaning of cancel parameter.void autoload_external_files(struct MPContext *mpctx, struct mp_cancel *cancel){ if (mpctx->opts->sub_auto < 0 && mpctx->opts->audiofile_auto < 0) return; if (!mpctx->opts->autoload_files || strcmp(mpctx->filename, "-") == 0) return;
void *tmp = talloc_new(NULL); struct subfn *list = find_external_files(mpctx->global, mpctx->filename, mpctx->opts); talloc_steal(tmp, list);
int sc[STREAM_TYPE_COUNT] = {0}; for (int n = 0; n < mpctx->num_tracks; n++) { if (!mpctx->tracks[n]->attached_picture) sc[mpctx->tracks[n]->type]++; }
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_tracks; n++) { struct track *t = mpctx->tracks[n]; if (t->demuxer && strcmp(t->demuxer->filename, filename) == 0) goto skip; } if (list[i].type == STREAM_SUB && !sc[STREAM_VIDEO] && !sc[STREAM_AUDIO]) goto skip; if (list[i].type == STREAM_AUDIO && !sc[STREAM_VIDEO]) goto skip; int first = mp_add_external_file(mpctx, filename, list[i].type, cancel); if (first < 0) goto skip;
for (int n = first; n < mpctx->num_tracks; n++) { struct track *t = mpctx->tracks[n]; t->auto_loaded = true; if (!t->lang) t->lang = talloc_strdup(t, lang); } skip:; }
talloc_free(tmp);}
// Do stuff to a newly loaded playlist. This includes any processing that may// be required after loading a playlist.void prepare_playlist(struct MPContext *mpctx, struct playlist *pl){ struct MPOpts *opts = mpctx->opts;
pl->current = NULL;
if (opts->playlist_pos >= 0) pl->current = playlist_entry_from_index(pl, opts->playlist_pos);
if (opts->shuffle) playlist_shuffle(pl);
if (opts->merge_files) merge_playlist_files(pl);
if (!pl->current) pl->current = mp_check_playlist_resume(mpctx, pl);
if (!pl->current) pl->current = playlist_get_first(pl);}
// 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->num_entries) { prepare_playlist(mpctx, pl); struct playlist_entry *new = pl->current; if (mpctx->playlist->current) playlist_add_redirect(pl, mpctx->playlist->current->filename); playlist_transfer_entries(mpctx->playlist, pl); // current entry is replaced if (mpctx->playlist->current) playlist_remove(mpctx->playlist, mpctx->playlist->current); if (new) mpctx->playlist->current = new; } else { MP_WARN(mpctx, "Empty playlist!\n"); }}
static void process_hooks(struct MPContext *mpctx, char *name){ mp_hook_start(mpctx, name);
while (!mp_hook_test_completion(mpctx, name)) { mp_idle(mpctx);
// We have no idea what blocks a hook, so just do a full abort. if (mpctx->stop_play) mp_abort_playback_async(mpctx); }}
// to be run on a worker thread, locked (temporarily unlocks core)static void load_chapters(struct MPContext *mpctx){ struct demuxer *src = mpctx->demuxer; bool free_src = false; char *chapter_file = mpctx->opts->chapter_file; if (chapter_file && chapter_file[0]) { chapter_file = talloc_strdup(NULL, chapter_file); mp_core_unlock(mpctx); struct demuxer_params p = {.stream_flags = STREAM_ORIGIN_DIRECT}; struct demuxer *demux = demux_open_url(chapter_file, &p, mpctx->playback_abort, mpctx->global); mp_core_lock(mpctx); if (demux) { src = demux; free_src = true; } talloc_free(mpctx->chapters); mpctx->chapters = NULL; talloc_free(chapter_file); } if (src && !mpctx->chapters) { talloc_free(mpctx->chapters); mpctx->num_chapters = src->num_chapters; mpctx->chapters = demux_copy_chapter_data(src->chapters, src->num_chapters); if (mpctx->opts->rebase_start_time) { for (int n = 0; n < mpctx->num_chapters; n++) mpctx->chapters[n].pts -= src->start_time; } } if (free_src) demux_cancel_and_free(src);}
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_cli(conf, params[n].name, params[n].value, M_SETOPT_BACKUP); }}
static void *open_demux_thread(void *ctx){ struct MPContext *mpctx = ctx;
mpthread_set_name("opener");
struct demuxer_params p = { .force_format = mpctx->open_format, .stream_flags = mpctx->open_url_flags, .stream_record = true, .is_top_level = true, }; struct demuxer *demux = demux_open_url(mpctx->open_url, &p, mpctx->open_cancel, mpctx->global); mpctx->open_res_demuxer = demux;
if (demux) { MP_VERBOSE(mpctx, "Opening done: %s\n", mpctx->open_url);
if (mpctx->open_for_prefetch && !demux->fully_read) { int num_streams = demux_get_num_stream(demux); for (int n = 0; n < num_streams; n++) { struct sh_stream *sh = demux_get_stream(demux, n); demuxer_select_track(demux, sh, MP_NOPTS_VALUE, true); }
demux_set_wakeup_cb(demux, wakeup_demux, mpctx); demux_start_thread(demux); demux_start_prefetch(demux); } } else { MP_VERBOSE(mpctx, "Opening failed or was aborted: %s\n", mpctx->open_url);
if (p.demuxer_failed) { mpctx->open_res_error = MPV_ERROR_UNKNOWN_FORMAT; } else { mpctx->open_res_error = MPV_ERROR_LOADING_FAILED; } }
atomic_store(&mpctx->open_done, true); mp_wakeup_core(mpctx); return NULL;}
static void cancel_open(struct MPContext *mpctx){ if (mpctx->open_cancel) mp_cancel_trigger(mpctx->open_cancel);
if (mpctx->open_active) pthread_join(mpctx->open_thread, NULL); mpctx->open_active = false;
if (mpctx->open_res_demuxer) demux_cancel_and_free(mpctx->open_res_demuxer); mpctx->open_res_demuxer = NULL;
TA_FREEP(&mpctx->open_cancel); TA_FREEP(&mpctx->open_url); TA_FREEP(&mpctx->open_format);
atomic_store(&mpctx->open_done, false);}
// Setup all the field to open this url, and make sure a thread is running.static void start_open(struct MPContext *mpctx, char *url, int url_flags, bool for_prefetch){ cancel_open(mpctx);
assert(!mpctx->open_active); assert(!mpctx->open_cancel); assert(!mpctx->open_res_demuxer); assert(!atomic_load(&mpctx->open_done));
mpctx->open_cancel = mp_cancel_new(NULL); mpctx->open_url = talloc_strdup(NULL, url); mpctx->open_format = talloc_strdup(NULL, mpctx->opts->demuxer_name); mpctx->open_url_flags = url_flags; mpctx->open_for_prefetch = for_prefetch && mpctx->opts->demuxer_thread;
if (pthread_create(&mpctx->open_thread, NULL, open_demux_thread, mpctx)) { cancel_open(mpctx); return; }
mpctx->open_active = true;}
static void open_demux_reentrant(struct MPContext *mpctx){ char *url = mpctx->stream_open_filename;
if (mpctx->open_active) { bool done = atomic_load(&mpctx->open_done); bool failed = done && !mpctx->open_res_demuxer; bool correct_url = strcmp(mpctx->open_url, url) == 0;
if (correct_url && !failed) { MP_VERBOSE(mpctx, "Using prefetched/prefetching URL.\n"); } else if (correct_url && failed) { MP_VERBOSE(mpctx, "Prefetched URL failed, retrying.\n"); cancel_open(mpctx); } else { if (done) { MP_VERBOSE(mpctx, "Dropping finished prefetch of wrong URL.\n"); } else { MP_VERBOSE(mpctx, "Aborting ongoing prefetch of wrong URL.\n"); } cancel_open(mpctx); } }
if (!mpctx->open_active) start_open(mpctx, url, mpctx->playing->stream_flags, false);
// User abort should cancel the opener now. mp_cancel_set_parent(mpctx->open_cancel, mpctx->playback_abort);
while (!atomic_load(&mpctx->open_done)) { mp_idle(mpctx);
if (mpctx->stop_play) mp_abort_playback_async(mpctx); }
if (mpctx->open_res_demuxer) { mpctx->demuxer = mpctx->open_res_demuxer; mpctx->open_res_demuxer = NULL; mp_cancel_set_parent(mpctx->demuxer->cancel, mpctx->playback_abort); } else { mpctx->error_playing = mpctx->open_res_error; }
cancel_open(mpctx); // cleanup}
void prefetch_next(struct MPContext *mpctx){ if (!mpctx->opts->prefetch_open) return;
struct playlist_entry *new_entry = mp_next_file(mpctx, +1, false, false); if (new_entry && !mpctx->open_active && new_entry->filename) { MP_VERBOSE(mpctx, "Prefetching: %s\n", new_entry->filename); start_open(mpctx, new_entry->filename, new_entry->stream_flags, true); }}
// Destroy the complex filter, and remove the references to the filter pads.// (Call cleanup_deassociated_complex_filters() to close decoders/VO/AO// that are not connected anymore due to this.)static void deassociate_complex_filters(struct MPContext *mpctx){ for (int n = 0; n < mpctx->num_tracks; n++) mpctx->tracks[n]->sink = NULL; if (mpctx->vo_chain) mpctx->vo_chain->filter_src = NULL; if (mpctx->ao_chain) mpctx->ao_chain->filter_src = NULL; TA_FREEP(&mpctx->lavfi); TA_FREEP(&mpctx->lavfi_graph);}
// Close all decoders and sinks (AO/VO) that are not connected to either// a track or a filter pad.static void cleanup_deassociated_complex_filters(struct MPContext *mpctx){ for (int n = 0; n < mpctx->num_tracks; n++) { struct track *track = mpctx->tracks[n]; if (!(track->sink || track->vo_c || track->ao_c)) { if (track->dec && !track->vo_c && !track->ao_c) { talloc_free(track->dec->f); track->dec = NULL; } track->selected = false; } }
if (mpctx->vo_chain && !mpctx->vo_chain->dec_src && !mpctx->vo_chain->filter_src) { uninit_video_chain(mpctx); } if (mpctx->ao_chain && !mpctx->ao_chain->dec_src && !mpctx->ao_chain->filter_src) { uninit_audio_chain(mpctx); }}
static void kill_outputs(struct MPContext *mpctx, struct track *track){ if (track->vo_c || track->ao_c) { MP_VERBOSE(mpctx, "deselecting track %d for lavfi-complex option\n", track->user_tid); mp_switch_track(mpctx, track->type, NULL, 0); } assert(!(track->vo_c || track->ao_c));}
// >0: changed, 0: no change, -1: errorstatic int reinit_complex_filters(struct MPContext *mpctx, bool force_uninit){ char *graph = mpctx->opts->lavfi_complex; bool have_graph = graph && graph[0] && !force_uninit; if (have_graph && mpctx->lavfi && strcmp(graph, mpctx->lavfi_graph) == 0 && !mp_filter_has_failed(mpctx->lavfi)) return 0; if (!mpctx->lavfi && !have_graph) return 0;
// Deassociate the old filter pads. We leave both sources (tracks) and // sinks (AO/VO) "dangling", connected to neither track or filter pad. // Later, we either reassociate them with new pads, or uninit them if // they are still dangling. This avoids too interruptive actions like // recreating the VO. deassociate_complex_filters(mpctx);
bool success = false; if (!have_graph) { success = true; // normal full removal of graph goto done; }
struct mp_lavfi *l = mp_lavfi_create_graph(mpctx->filter_root, 0, false, NULL, graph); if (!l) goto done; mpctx->lavfi = l->f; mpctx->lavfi_graph = talloc_strdup(NULL, graph);
mp_filter_set_error_handler(mpctx->lavfi, mpctx->filter_root);
for (int n = 0; n < mpctx->lavfi->num_pins; n++) mp_pin_disconnect(mpctx->lavfi->pins[n]);
struct mp_pin *pad = mp_filter_get_named_pin(mpctx->lavfi, "vo"); if (pad && mp_pin_get_dir(pad) == MP_PIN_OUT) { if (mpctx->vo_chain && mpctx->vo_chain->track) kill_outputs(mpctx, mpctx->vo_chain->track); if (!mpctx->vo_chain) { reinit_video_chain_src(mpctx, NULL); if (!mpctx->vo_chain) goto done; } struct vo_chain *vo_c = mpctx->vo_chain; assert(!vo_c->track); vo_c->filter_src = pad; mp_pin_connect(vo_c->filter->f->pins[0], vo_c->filter_src); }
pad = mp_filter_get_named_pin(mpctx->lavfi, "ao"); if (pad && mp_pin_get_dir(pad) == MP_PIN_OUT) { if (mpctx->ao_chain && mpctx->ao_chain->track) kill_outputs(mpctx, mpctx->ao_chain->track); if (!mpctx->ao_chain) { reinit_audio_chain_src(mpctx, NULL); if (!mpctx->ao_chain) goto done; } struct ao_chain *ao_c = mpctx->ao_chain; assert(!ao_c->track); ao_c->filter_src = pad; mp_pin_connect(ao_c->filter->f->pins[0], ao_c->filter_src); }
for (int n = 0; n < mpctx->num_tracks; n++) { struct track *track = mpctx->tracks[n];
char label[32]; char prefix; switch (track->type) { case STREAM_VIDEO: prefix = 'v'; break; case STREAM_AUDIO: prefix = 'a'; break; default: continue; } snprintf(label, sizeof(label), "%cid%d", prefix, track->user_tid);
pad = mp_filter_get_named_pin(mpctx->lavfi, label); if (!pad) continue; if (mp_pin_get_dir(pad) != MP_PIN_IN) continue; assert(!mp_pin_is_connected(pad));
assert(!track->sink);
kill_outputs(mpctx, track);
track->sink = pad; track->selected = true;
if (!track->dec) { if (track->type == STREAM_VIDEO && !init_video_decoder(mpctx, track)) goto done; if (track->type == STREAM_AUDIO && !init_audio_decoder(mpctx, track)) goto done; }
mp_pin_connect(track->sink, track->dec->f->pins[0]); }
// Don't allow unconnected pins. Libavfilter would make the data flow a // real pain anyway. for (int n = 0; n < mpctx->lavfi->num_pins; n++) { struct mp_pin *pin = mpctx->lavfi->pins[n]; if (!mp_pin_is_connected(pin)) { MP_ERR(mpctx, "Pad %s is not connected to anything.\n", mp_pin_get_name(pin)); goto done; } }
success = true;done:
if (!success) deassociate_complex_filters(mpctx);
cleanup_deassociated_complex_filters(mpctx);
if (mpctx->playback_initialized) { for (int n = 0; n < mpctx->num_tracks; n++) reselect_demux_stream(mpctx, mpctx->tracks[n]); }
mp_notify(mpctx, MPV_EVENT_TRACKS_CHANGED, NULL);
return success ? 1 : -1;}
void update_lavfi_complex(struct MPContext *mpctx){ if (mpctx->playback_initialized) { if (reinit_complex_filters(mpctx, false) != 0) issue_refresh_seek(mpctx, MPSEEK_EXACT); }}
// Worker thread for loading external files and such. This is needed to avoid// freezing the core when waiting for network while loading these.static void load_external_opts_thread(void *p){ void **a = p; struct MPContext *mpctx = a[0]; struct mp_waiter *waiter = a[1];
mp_core_lock(mpctx);
load_chapters(mpctx); open_external_files(mpctx, mpctx->opts->audio_files, STREAM_AUDIO); open_external_files(mpctx, mpctx->opts->sub_name, STREAM_SUB); open_external_files(mpctx, mpctx->opts->external_files, STREAM_TYPE_COUNT); autoload_external_files(mpctx, mpctx->playback_abort);
mp_waiter_wakeup(waiter, 0); mp_wakeup_core(mpctx); mp_core_unlock(mpctx);}
static void load_external_opts(struct MPContext *mpctx){ struct mp_waiter wait = MP_WAITER_INITIALIZER;
void *a[] = {mpctx, &wait}; if (!mp_thread_pool_queue(mpctx->thread_pool, load_external_opts_thread, a)) { mpctx->stop_play = PT_ERROR; return; }
while (!mp_waiter_poll(&wait)) { mp_idle(mpctx);
if (mpctx->stop_play) mp_abort_playback_async(mpctx); }
mp_waiter_wait(&wait);}
// 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;
assert(mpctx->stop_play);
mp_notify(mpctx, MPV_EVENT_START_FILE, NULL);
mp_cancel_reset(mpctx->playback_abort);
mpctx->error_playing = MPV_ERROR_LOADING_FAILED; mpctx->stop_play = 0; mpctx->filename = NULL; mpctx->shown_aframes = 0; mpctx->shown_vframes = 0; mpctx->last_vo_pts = MP_NOPTS_VALUE; mpctx->last_chapter_seek = -2; mpctx->last_chapter_pts = MP_NOPTS_VALUE; mpctx->last_chapter = -2; mpctx->paused = false; mpctx->playing_msg_shown = false; mpctx->max_frames = -1; mpctx->video_speed = mpctx->audio_speed = opts->playback_speed; mpctx->speed_factor_a = mpctx->speed_factor_v = 1.0; mpctx->display_sync_error = 0.0; mpctx->display_sync_active = false; // let get_current_time() show 0 as start time (before playback_pts is set) mpctx->last_seek_pts = 0.0; mpctx->seek = (struct seek_params){ 0 }; mpctx->filter_root = mp_filter_create_root(mpctx->global); mp_filter_root_set_wakeup_cb(mpctx->filter_root, mp_wakeup_core_cb, mpctx);
reset_playback_state(mpctx);
mpctx->playing = mpctx->playlist->current; if (!mpctx->playing || !mpctx->playing->filename) goto terminate_playback; mpctx->playing->reserved += 1;
mpctx->filename = talloc_strdup(NULL, mpctx->playing->filename); mpctx->stream_open_filename = mpctx->filename;
mpctx->add_osd_seek_info &= OSD_SEEK_INFO_CURRENT_FILE;
if (opts->reset_options) { for (int n = 0; opts->reset_options[n]; n++) { const char *opt = opts->reset_options[n]; if (opt[0]) { if (strcmp(opt, "all") == 0) { m_config_backup_all_opts(mpctx->mconfig); } else { m_config_backup_opt(mpctx->mconfig, opt); } } } }
mp_load_auto_profiles(mpctx);
mp_load_playback_resume(mpctx, mpctx->filename);
load_per_file_options(mpctx->mconfig, mpctx->playing->params, mpctx->playing->num_params);
mpctx->max_frames = opts->play_frames;
handle_force_window(mpctx, false);
if (mpctx->playlist->num_entries > 1 || mpctx->playing->num_redirects) MP_INFO(mpctx, "Playing: %s\n", mpctx->filename);
assert(mpctx->demuxer == NULL);
process_hooks(mpctx, "on_load"); if (mpctx->stop_play) goto terminate_playback;
if (opts->stream_dump && opts->stream_dump[0]) { if (stream_dump(mpctx, mpctx->stream_open_filename) >= 0) mpctx->error_playing = 1; goto terminate_playback; }
open_demux_reentrant(mpctx); if (!mpctx->stop_play && !mpctx->demuxer) { process_hooks(mpctx, "on_load_fail"); if (strcmp(mpctx->stream_open_filename, mpctx->filename) != 0 && !mpctx->stop_play) { mpctx->error_playing = MPV_ERROR_LOADING_FAILED; open_demux_reentrant(mpctx); } } if (!mpctx->demuxer || mpctx->stop_play) goto terminate_playback;
if (mpctx->demuxer->playlist) { struct playlist *pl = mpctx->demuxer->playlist; transfer_playlist(mpctx, pl); mp_notify_property(mpctx, "playlist"); mpctx->error_playing = 2; goto terminate_playback; }
if (mpctx->opts->rebase_start_time) demux_set_ts_offset(mpctx->demuxer, -mpctx->demuxer->start_time); enable_demux_thread(mpctx, mpctx->demuxer);
add_demuxer_tracks(mpctx, mpctx->demuxer);
load_external_opts(mpctx); if (mpctx->stop_play) goto terminate_playback;
check_previous_track_selection(mpctx);
process_hooks(mpctx, "on_preloaded"); if (mpctx->stop_play) goto terminate_playback;
if (reinit_complex_filters(mpctx, false) < 0) goto terminate_playback;
assert(NUM_PTRACKS == 2); // opts->stream_id is hardcoded to 2 for (int t = 0; t < STREAM_TYPE_COUNT; t++) { for (int i = 0; i < NUM_PTRACKS; i++) { struct track *sel = NULL; bool taken = (t == STREAM_VIDEO && mpctx->vo_chain) || (t == STREAM_AUDIO && mpctx->ao_chain); if (!taken && opts->stream_auto_sel) sel = select_default_track(mpctx, i, t); mpctx->current_track[i][t] = sel; } } for (int t = 0; t < STREAM_TYPE_COUNT; t++) { for (int i = 0; i < NUM_PTRACKS; i++) { struct track *track = mpctx->current_track[i][t]; if (track) { if (track->selected) { MP_ERR(mpctx, "Track %d can't be selected twice.\n", track->user_tid); mpctx->current_track[i][t] = NULL; } else { track->selected = true; } } } }
for (int n = 0; n < mpctx->num_tracks; n++) reselect_demux_stream(mpctx, mpctx->tracks[n]);
update_demuxer_properties(mpctx);
update_playback_speed(mpctx);
reinit_video_chain(mpctx); reinit_audio_chain(mpctx); reinit_sub_all(mpctx);
if (mpctx->encode_lavc_ctx) { if (mpctx->vo_chain) encode_lavc_expect_stream(mpctx->encode_lavc_ctx, STREAM_VIDEO); if (mpctx->ao_chain) encode_lavc_expect_stream(mpctx->encode_lavc_ctx, STREAM_AUDIO); encode_lavc_set_metadata(mpctx->encode_lavc_ctx, mpctx->demuxer->metadata); }
if (!mpctx->vo_chain && !mpctx->ao_chain && opts->stream_auto_sel) { MP_FATAL(mpctx, "No video or audio streams selected.\n"); mpctx->error_playing = MPV_ERROR_NOTHING_TO_PLAY; goto terminate_playback; }
if (mpctx->vo_chain && mpctx->vo_chain->is_coverart) { MP_INFO(mpctx, "Displaying attached picture. Use --no-audio-display to prevent this.\n"); }
if (!mpctx->vo_chain) handle_force_window(mpctx, true);
MP_VERBOSE(mpctx, "Starting playback...\n");
mpctx->playback_initialized = true; mp_notify(mpctx, MPV_EVENT_FILE_LOADED, NULL); update_screensaver_state(mpctx);
if (mpctx->max_frames == 0) { if (!mpctx->stop_play) mpctx->stop_play = PT_NEXT_ENTRY; mpctx->error_playing = 0; goto terminate_playback; }
if (opts->demuxer_cache_wait) { demux_start_prefetch(mpctx->demuxer);
while (!mpctx->stop_play) { struct demux_reader_state s; demux_get_reader_state(mpctx->demuxer, &s); if (s.idle) break;
mp_idle(mpctx); } }
// (Not get_play_start_pts(), which would always trigger a seek.) double play_start_pts = rel_time_to_abs(mpctx, opts->play_start);
// Backward playback -> start from end by default. if (play_start_pts == MP_NOPTS_VALUE && opts->play_dir < 0) play_start_pts = get_start_time(mpctx, -1);
if (play_start_pts != MP_NOPTS_VALUE) { queue_seek(mpctx, MPSEEK_ABSOLUTE, play_start_pts, MPSEEK_DEFAULT, 0); execute_queued_seek(mpctx); }
update_internal_pause_state(mpctx);
open_recorder(mpctx, true);
playback_start = mp_time_sec(); mpctx->error_playing = 0; mpctx->in_playloop = true; while (!mpctx->stop_play) run_playloop(mpctx); mpctx->in_playloop = false;
MP_VERBOSE(mpctx, "EOF code: %d \n", mpctx->stop_play);
terminate_playback:
if (!mpctx->stop_play) mpctx->stop_play = PT_ERROR;
if (mpctx->stop_play != AT_END_OF_FILE) clear_audio_output_buffers(mpctx);
update_core_idle_state(mpctx);
process_hooks(mpctx, "on_unload");
if (mpctx->step_frames) { opts->pause = 1; m_config_notify_change_opt_ptr(mpctx->mconfig, &opts->pause); }
close_recorder(mpctx);
// time to uninit all, except global stuff: reinit_complex_filters(mpctx, true); uninit_audio_chain(mpctx); uninit_video_chain(mpctx); uninit_sub_all(mpctx); if (!opts->gapless_audio && !mpctx->encode_lavc_ctx) uninit_audio_out(mpctx);
mpctx->playback_initialized = false;
uninit_demuxer(mpctx);
// Possibly stop ongoing async commands. mp_abort_playback_async(mpctx);
m_config_restore_backups(mpctx->mconfig);
TA_FREEP(&mpctx->filter_root); talloc_free(mpctx->filtered_tags); mpctx->filtered_tags = NULL;
mp_notify(mpctx, MPV_EVENT_TRACKS_CHANGED, NULL);
bool nothing_played = !mpctx->shown_aframes && !mpctx->shown_vframes && mpctx->error_playing <= 0; struct mpv_event_end_file end_event = {0}; switch (mpctx->stop_play) { case PT_ERROR: case AT_END_OF_FILE: { if (mpctx->error_playing == 0 && nothing_played) mpctx->error_playing = MPV_ERROR_NOTHING_TO_PLAY; if (mpctx->error_playing < 0) { end_event.error = mpctx->error_playing; end_event.reason = MPV_END_FILE_REASON_ERROR; } else if (mpctx->error_playing == 2) { end_event.reason = MPV_END_FILE_REASON_REDIRECT; } else { end_event.reason = MPV_END_FILE_REASON_EOF; } if (mpctx->playing) { // Played/paused for longer than 1 second -> ok mpctx->playing->playback_short = playback_start < 0 || mp_time_sec() - playback_start < 1.0; mpctx->playing->init_failed = nothing_played; } break; } // Note that error_playing is meaningless in these cases. case PT_NEXT_ENTRY: case PT_CURRENT_ENTRY: case PT_STOP: end_event.reason = MPV_END_FILE_REASON_STOP; break; case PT_QUIT: end_event.reason = MPV_END_FILE_REASON_QUIT; break; }; mp_notify(mpctx, MPV_EVENT_END_FILE, &end_event);
MP_VERBOSE(mpctx, "finished playback, %s (reason %d)\n", mpv_error_string(end_event.error), end_event.reason); if (end_event.error == MPV_ERROR_UNKNOWN_FORMAT) MP_ERR(mpctx, "Failed to recognize file format.\n"); MP_INFO(mpctx, "\n");
if (mpctx->playing) playlist_entry_unref(mpctx->playing); mpctx->playing = NULL; talloc_free(mpctx->filename); mpctx->filename = NULL; mpctx->stream_open_filename = NULL;
if (end_event.error < 0 && nothing_played) { mpctx->files_broken++; } else if (end_event.error < 0) { mpctx->files_errored++; } else { mpctx->files_played++; }
assert(mpctx->stop_play);}
// Determine the next file to play. Note that if this function returns non-NULL,// it can have side-effects and mutate mpctx.// direction: -1 (previous) or +1 (next)// force: if true, don't skip playlist entries marked as failed// mutate: if true, change loop countersstruct playlist_entry *mp_next_file(struct MPContext *mpctx, int direction, bool force, bool mutate){ struct playlist_entry *next = playlist_get_next(mpctx->playlist, direction); if (next && direction < 0 && !force) { // Don't jump to files that would immediately go to next file anyway while (next && next->playback_short) next = playlist_entry_get_rel(next, -1); // Always allow jumping to first file if (!next && mpctx->opts->loop_times == 1) next = playlist_get_first(mpctx->playlist); } if (!next && mpctx->opts->loop_times != 1) { if (direction > 0) { if (mpctx->opts->shuffle) playlist_shuffle(mpctx->playlist); next = playlist_get_first(mpctx->playlist); if (next && mpctx->opts->loop_times > 1) { mpctx->opts->loop_times--; m_config_notify_change_opt_ptr(mpctx->mconfig, &mpctx->opts->loop_times); } } else { next = playlist_get_last(mpctx->playlist); // Don't jump to files that would immediately go to next file anyway while (next && next->playback_short) next = playlist_entry_get_rel(next, -1); } bool ignore_failures = mpctx->opts->loop_times == -2; if (!force && next && next->init_failed && !ignore_failures) { // Don't endless loop if no file in playlist is playable bool all_failed = true; for (int n = 0; n < mpctx->playlist->num_entries; n++) { all_failed &= mpctx->playlist->entries[n]->init_failed; if (!all_failed) break; } if (all_failed) next = NULL; } } return next;}
// Play all entries on the playlist, starting from the current entry.// Return if all done.void mp_play_files(struct MPContext *mpctx){ // Wait for all scripts to load before possibly starting playback. if (!mp_clients_all_initialized(mpctx)) { MP_VERBOSE(mpctx, "Waiting for scripts...\n"); while (!mp_clients_all_initialized(mpctx)) mp_idle(mpctx); mp_wakeup_core(mpctx); // avoid lost wakeups during waiting MP_VERBOSE(mpctx, "Done loading scripts.\n"); } // After above is finished; but even if it's skipped. mp_msg_set_early_logging(mpctx->global, false);
prepare_playlist(mpctx, mpctx->playlist);
for (;;) { assert(mpctx->stop_play); idle_loop(mpctx); if (mpctx->stop_play == PT_QUIT) break;
play_current_file(mpctx); if (mpctx->stop_play == PT_QUIT) break;
struct playlist_entry *new_entry = mpctx->playlist->current; if (mpctx->stop_play == PT_NEXT_ENTRY || mpctx->stop_play == PT_ERROR || mpctx->stop_play == AT_END_OF_FILE || mpctx->stop_play == PT_STOP) { new_entry = mp_next_file(mpctx, +1, false, true); }
mpctx->playlist->current = new_entry; mpctx->playlist->current_was_replaced = false; mpctx->stop_play = PT_STOP;
if (!mpctx->playlist->current && mpctx->opts->player_idle_mode < 2) break; }
cancel_open(mpctx);
if (mpctx->encode_lavc_ctx) { // Make sure all streams get finished. uninit_audio_out(mpctx); uninit_video_out(mpctx);
if (!encode_lavc_free(mpctx->encode_lavc_ctx)) mpctx->stop_play = PT_ERROR;
mpctx->encode_lavc_ctx = NULL; }}
// Abort current playback and set the given entry to play next.// e must be on the mpctx->playlist.void mp_set_playlist_entry(struct MPContext *mpctx, struct playlist_entry *e){ assert(!e || playlist_entry_to_index(mpctx->playlist, e) >= 0); mpctx->playlist->current = e; mpctx->playlist->current_was_replaced = false; // Make it pick up the new entry. if (mpctx->stop_play != PT_QUIT) mpctx->stop_play = PT_CURRENT_ENTRY; mp_wakeup_core(mpctx);}
static void set_track_recorder_sink(struct track *track, struct mp_recorder_sink *sink){ if (track->d_sub) sub_set_recorder_sink(track->d_sub, sink); if (track->dec) track->dec->recorder_sink = sink; track->remux_sink = sink;}
void close_recorder(struct MPContext *mpctx){ if (!mpctx->recorder) return;
for (int n = 0; n < mpctx->num_tracks; n++) set_track_recorder_sink(mpctx->tracks[n], NULL);
mp_recorder_destroy(mpctx->recorder); mpctx->recorder = NULL;}
// Like close_recorder(), but also unset the option. Intended for use on errors.void close_recorder_and_error(struct MPContext *mpctx){ close_recorder(mpctx); talloc_free(mpctx->opts->record_file); mpctx->opts->record_file = NULL; m_config_notify_change_opt_ptr(mpctx->mconfig, &mpctx->opts->record_file); MP_ERR(mpctx, "Disabling stream recording.\n");}
void open_recorder(struct MPContext *mpctx, bool on_init){ if (!mpctx->playback_initialized) return;
close_recorder(mpctx);
char *target = mpctx->opts->record_file; if (!target || !target[0]) return;
struct sh_stream **streams = NULL; int num_streams = 0;
for (int n = 0; n < mpctx->num_tracks; n++) { struct track *track = mpctx->tracks[n]; if (track->stream && track->selected && (track->d_sub || track->dec)) MP_TARRAY_APPEND(NULL, streams, num_streams, track->stream); }
mpctx->recorder = mp_recorder_create(mpctx->global, mpctx->opts->record_file, streams, num_streams);
if (!mpctx->recorder) { talloc_free(streams); close_recorder_and_error(mpctx); return; }
if (!on_init) mp_recorder_mark_discontinuity(mpctx->recorder);
int n_stream = 0; for (int n = 0; n < mpctx->num_tracks; n++) { struct track *track = mpctx->tracks[n]; if (n_stream >= num_streams) break; // (We expect track->stream not to be reused on other tracks.) if (track->stream == streams[n_stream]) { struct mp_recorder_sink * sink = mp_recorder_get_sink(mpctx->recorder, streams[n_stream]); assert(sink); set_track_recorder_sink(track, sink); n_stream++; } }
talloc_free(streams);}