813 lines · 28.7 KB · JavaScript
RawDownload
"use strict";(function main_default_js(g) {// - g is the global object.// - User callbacks called without 'this', global only if callee is non-strict.// - The names of function expressions are not required, but are used in stack//   traces. We name them where useful to show up (fname:#line always shows). mp.msg = { log: mp.log };mp.msg.verbose = mp.log.bind(null, "v");var levels = ["fatal", "error", "warn", "info", "debug", "trace"];levels.forEach(function(l) { mp.msg[l] = mp.log.bind(null, l) }); // same as {} but without inherited stuff, e.g. o["toString"] doesn't exist.// used where we try to fetch items by keys which we don't absolutely trust.function new_cache() {    return Object.create(null, {});} /********************************************************************** *  event handlers, property observers, idle, client messages, hooks, async *********************************************************************/var ehandlers = new_cache() // items of event-name: array of {maybe cb: fn} mp.register_event = function(name, fn) {    if (!ehandlers[name])        ehandlers[name] = [];    ehandlers[name] = ehandlers[name].concat([{cb: fn}]);  // replaces the arr    return mp._request_event(name, true);} mp.unregister_event = function(fn) {    for (var name in ehandlers) {        ehandlers[name] = ehandlers[name].filter(function(h) {                              if (h.cb != fn)                                  return true;                              delete h.cb;  // dispatch could have a ref to h                          });  // replacing, not mutating the array        if (!ehandlers[name].length) {            delete ehandlers[name];            mp._request_event(name, false);        }    }} // call only pre-registered handlers, but not ones which got unregisteredfunction dispatch_event(e) {    var handlers = ehandlers[e.event];    if (handlers) {        for (var len = handlers.length, i = 0; i < len; i++) {            var cb = handlers[i].cb;  // 'handlers' won't mutate, but unregister            if (cb)                   // could remove cb from some items                cb(e);        }    }} //  ----- idle observers -----var iobservers = [],  // array of callbacks    ideleted = false; mp.register_idle = function(fn) {    iobservers.push(fn);} mp.unregister_idle = function(fn) {    iobservers.forEach(function(f, i) {        if (f == fn)             delete iobservers[i];  // -> same length but [more] sparse    });    ideleted = true;} function notify_idle_observers() {    // forEach and filter skip deleted items and newly added items    iobservers.forEach(function(f) { f() });    if (ideleted) {        iobservers = iobservers.filter(function() { return true });        ideleted = false;    }} //  ----- property observers -----var next_oid = 1,    observers = new_cache();  // items of id: fn mp.observe_property = function(name, format, fn) {    var id = next_oid++;    observers[id] = fn;    return mp._observe_property(id, name, format || undefined);  // allow null} mp.unobserve_property = function(fn) {    for (var id in observers) {        if (observers[id] == fn) {            delete observers[id];            mp._unobserve_property(id);        }    }} function notify_observer(e) {    var cb = observers[e.id];    if (cb)        cb(e.name, e.data);} //  -----  Client messages -----var messages = new_cache();  // items of name: fn // overrides name. no libmpv API to reg/unreg specific messages.mp.register_script_message = function(name, fn) {    messages[name] = fn;} mp.unregister_script_message = function(name) {    delete messages[name];} function dispatch_message(ev) {    var cb = ev.args.length ? messages[ev.args[0]] : false;    if (cb)        cb.apply(null, ev.args.slice(1));} //  ----- hooks -----var hooks = [];  // array of callbacks, id is index+1 function run_hook(ev) {    var state = 0;  // 0:initial, 1:deferred, 2:continued    function do_cont() { return state = 2, mp._hook_continue(ev.hook_id) }     function err() { return mp.msg.error("hook already continued"), undefined }    function usr_defer() { return state == 2 ? err() : (state = 1, true) }    function usr_cont()  { return state == 2 ? err() : do_cont() }     var cb = ev.id > 0 && hooks[ev.id - 1];    if (cb)        cb({ defer: usr_defer, cont: usr_cont });    return state == 0 ? do_cont() : true;} mp.add_hook = function add_hook(name, pri, fn) {    hooks.push(fn);    // 50 (scripting docs default priority) maps to 0 (default in C API docs)    return mp._hook_add(name, pri - 50, hooks.length);} // ----- async commands -----var async_callbacks = new_cache();  // items of id: fnvar async_next_id = 1; mp.command_native_async = function command_native_async(node, cb) {    var id = async_next_id++;    cb = cb || function dummy() {};    if (!mp._command_native_async(id, node)) {        var le = mp.last_error();        setTimeout(cb, 0, false, undefined, le);  /* callback async */        mp._set_last_error(le);        return undefined;    }    async_callbacks[id] = cb;    return id;} function async_command_handler(ev) {    var cb = async_callbacks[ev.id];    delete async_callbacks[ev.id];    if (ev.error)        cb(false, undefined, ev.error);    else        cb(true, ev.result, "");} mp.abort_async_command = function abort_async_command(id) {    // cb will be invoked regardless, possibly with the abort result    if (async_callbacks[id])        mp._abort_async_command(id);} // osd-assvar next_assid = 1;mp.create_osd_overlay = function create_osd_overlay(format) {    return {        format: format || "ass-events",        id: next_assid++,        data: "",        res_x: 0,        res_y: 720,        z: 0,         update: function ass_update() {            var cmd = {};  // shallow clone of `this', excluding methods            for (var k in this) {                if (typeof this[k] != "function")                    cmd[k] = this[k];            }             cmd.name = "osd-overlay";            cmd.res_x = Math.round(this.res_x);            cmd.res_y = Math.round(this.res_y);             return mp.command_native(cmd);        },         remove: function ass_remove() {            mp.command_native({                name: "osd-overlay",                id: this.id,                format: "none",                data: "",            });            return mp.last_error() ? undefined : true;        },    };} // osd-ass legacy APImp.set_osd_ass = function set_osd_ass(res_x, res_y, data) {    if (!mp._legacy_overlay)        mp._legacy_overlay = mp.create_osd_overlay("ass-events");     var lo = mp._legacy_overlay;    if (lo.res_x == res_x && lo.res_y == res_y && lo.data == data)        return true;     mp._legacy_overlay.res_x = res_x;    mp._legacy_overlay.res_y = res_y;    mp._legacy_overlay.data = data;    return mp._legacy_overlay.update();} // the following return undefined on error, null passthrough, or legacy objectmp.get_osd_size = function get_osd_size() {    var d = mp.get_property_native("osd-dimensions");    return d && {width: d.w, height: d.h, aspect: d.aspect};}mp.get_osd_margins = function get_osd_margins() {    var d = mp.get_property_native("osd-dimensions");    return d && {left: d.ml, right: d.mr, top: d.mt, bottom: d.mb};} /********************************************************************** *  key bindings *********************************************************************/// binds: items of (binding) name which are objects of:// {cb: fn, forced: bool, maybe input: str, repeatable: bool, complex: bool}var binds = new_cache(); function dispatch_key_binding(name, state, key_name, key_text) {    var cb = binds[name] ? binds[name].cb : false;    if (cb)  // "script-binding [<script_name>/]<name>" command was invoked        cb(state, key_name, key_text);} var binds_tid = 0;  // flush timer id. actual id's are always true-thymp.flush_key_bindings = function flush_key_bindings() {    function prioritized_inputs(arr) {        return arr.sort(function(a, b) { return a.id - b.id })                  .map(function(bind) { return bind.input });    }     var def = [], forced = [];    for (var n in binds)        if (binds[n].input)            (binds[n].forced ? forced : def).push(binds[n]);    // newer bindings for the same key override/hide older ones    def = prioritized_inputs(def);    forced = prioritized_inputs(forced);     var sect = "input_" + mp.script_name;    mp.commandv("define-section", sect, def.join("\n"), "default");    mp.commandv("enable-section", sect, "allow-hide-cursor+allow-vo-dragging");     sect = "input_forced_" + mp.script_name;    mp.commandv("define-section", sect, forced.join("\n"), "force");    mp.commandv("enable-section", sect, "allow-hide-cursor+allow-vo-dragging");     clearTimeout(binds_tid);  // cancel future flush if called directly    binds_tid = 0;} function sched_bindings_flush() {    if (!binds_tid)        binds_tid = setTimeout(mp.flush_key_bindings, 0);  // fires on idle} // name/opts maybe omitted. opts: object with optional bool members: repeatable,// complex, forced, or a string str which is evaluated as object {str: true}.var next_bid = 1;function add_binding(forced, key, name, fn, opts) {    if (typeof name == "function") {  // as if "name" is not part of the args        opts = fn;        fn = name;        name = false;    }    var key_data = {forced: forced};    switch (typeof opts) {  // merge opts into key_data        case "string": key_data[opts] = true; break;        case "object": for (var o in opts) key_data[o] = opts[o];    }    key_data.id = next_bid++;    if (!name)        name = "__keybinding" + key_data.id;  // new unique binding name     if (key_data.complex) {        mp.register_script_message(name, function msg_cb() {            fn({event: "press", is_mouse: false});        });        var KEY_STATES = { u: "up", d: "down", r: "repeat", p: "press" };        key_data.cb = function key_cb(state, key_name, key_text) {            fn({                event: KEY_STATES[state[0]] || "unknown",                is_mouse: state[1] == "m",                canceled: state[2] == "c",                key_name: key_name || undefined,                key_text: key_text || undefined            });        }    } else {        mp.register_script_message(name, fn);        key_data.cb = function key_cb(state) {            // Emulate the semantics at input.c: mouse emits on up, kb on down.            // Also, key repeat triggers the binding again.            var e = state[0],                emit = (state[1] == "m") ? (e == "u") : (e == "d"),                canceled = state[2] == "c";            if (canceled)                return;            if (emit || e == "p" || e == "r" && key_data.repeatable)                fn();        }    }     if (key)        key_data.input = key + " script-binding " + mp.script_name + "/" + name;    binds[name] = key_data;  // used by user and/or our (key) script-binding    sched_bindings_flush();} mp.add_key_binding = add_binding.bind(null, false);mp.add_forced_key_binding = add_binding.bind(null, true); mp.remove_key_binding = function(name) {    mp.unregister_script_message(name);    delete binds[name];    sched_bindings_flush();} /********************************************************************** Timers: compatible HTML5 WindowTimers - set/clear Timeout/Interval - Spec: https://www.w3.org/TR/html5/webappapis.html#timers - Guaranteed to callback a-sync to [re-]insertion (event-loop wise). - Guaranteed to callback by expiration order, or, if equal, by insertion order. - Not guaranteed schedule accuracy, though intervals should have good average. *********************************************************************/ // pending 'timers' ordered by expiration: latest at index 0 (top fires first).// Earlier timers are quicker to handle - just push/pop or fewer items to shift.var next_tid = 1,    timers = [],  // while in process_timers, just insertion-ordered (push)    tset_is_push = false,  // signal set_timer that we're in process_timers    tcanceled = false,  // or object of items timer-id: true    now = mp.get_time_ms;  // just an alias function insert_sorted(arr, t) {    for (var i = arr.length - 1; i >= 0 && t.when >= arr[i].when; i--)        arr[i + 1] = arr[i];  // move up timers which fire earlier than t    arr[i + 1] = t;  // i is -1 or fires later than t} // args (is "arguments"): fn_or_str [,duration [,user_arg1 [, user_arg2 ...]]]function set_timer(repeat, args) {    var fos = args[0],        duration = Math.max(0, (args[1] || 0)),  // minimum and default are 0        t = {            id: next_tid++,            when: now() + duration,            interval: repeat ? duration : -1,            callback: (typeof fos == "function") ? fos : Function(fos),            args: (args.length < 3) ? false : [].slice.call(args, 2),        };     if (tset_is_push) {        timers.push(t);    } else {        insert_sorted(timers, t);    }    return t.id;} g.setTimeout  = function setTimeout()  { return set_timer(false, arguments) };g.setInterval = function setInterval() { return set_timer(true,  arguments) }; g.clearTimeout = g.clearInterval = function(id) {    if (id < next_tid) {  // must ignore if not active timer id.        if (!tcanceled)            tcanceled = {};        tcanceled[id] = true;    }} // arr: ordered timers array. ret: -1: no timers, 0: due, positive: ms to waitfunction peek_wait(arr) {    return arr.length ? Math.max(0, arr[arr.length - 1].when - now()) : -1;} function peek_timers_wait() {    return peek_wait(timers);  // must not be called while in process_timers} // Callback all due non-canceled timers which were inserted before calling us.// Returns wait in ms till the next timer (possibly 0), or -1 if nothing pends.function process_timers() {    var wait = peek_wait(timers);    if (wait != 0)        return wait;     var actives = timers;  // only process those already inserted by now    timers = [];  // we'll handle added new timers at the end of processing.    tset_is_push = true;  // signal set_timer to just push-insert     do {        var t = actives.pop();        if (tcanceled && tcanceled[t.id])            continue;         if (t.args) {            t.callback.apply(null, t.args);        } else {            (0, t.callback)();  // faster, nicer stack trace than t.cb.call()        }         if (t.interval >= 0) {            // allow 20 ms delay/clock-resolution/gc before we skip and reset            t.when = Math.max(now() - 20, t.when + t.interval);            timers.push(t);  // insertion order only        }    } while (peek_wait(actives) == 0);     // new 'timers' are insertion-ordered. remains of actives are fully ordered    timers.forEach(function(t) { insert_sorted(actives, t) });    timers = actives;  // now we're fully ordered again, and with all timers    tset_is_push = false;    if (tcanceled) {        timers = timers.filter(function(t) { return !tcanceled[t.id] });        tcanceled = false;    }    return peek_wait(timers);} /********************************************************************** CommonJS module/require  Spec: http://wiki.commonjs.org/wiki/Modules/1.1.1 - All the mandatory requirements are implemented, all the unit tests pass. - The implementation makes the following exception:   - Allows the chars [~@:\\] in module id for meta-dir/builtin/dos-drive/UNC.  Implementation choices beyond the specification: - A module may assign to module.exports (rather than only to exports). - A module's 'this' is the global object, also if it sets strict mode.   - No 'global'/'self'. Users can do "this.global = this;" before require(..)   - A module has "privacy of its top scope", runs in its own function context. - No id identity with symlinks - a valid choice which others make too. - require("X") always maps to "X.js" -> require("foo.js") is file "foo.js.js". - Global modules search paths are 'scripts/modules.js/' in mpv config dirs. - A main script could e.g. require("./abc") to load a non-global module. - Module id supports mpv path enhancements, e.g. ~/foo, ~~/bar, ~~desktop/baz *********************************************************************/ mp.module_paths = [];  // global modules search pathsif (mp.script_path !== undefined)  // loaded as a directory    mp.module_paths.push(mp.utils.join_path(mp.script_path, "modules")); // Internal meta top-dirs. Users should not rely on these names.var MODULES_META = "~~modules",    SCRIPTDIR_META = "~~scriptdir",  // relative script path -> meta absolute id    main_script = mp.utils.split_path(mp.script_file);  // -> [ path, file ] function resolve_module_file(id) {    var sep = id.indexOf("/"),        base = id.substring(0, sep),        rest = id.substring(sep + 1) + ".js";     if (base == SCRIPTDIR_META)        return mp.utils.join_path(main_script[0], rest);     if (base == MODULES_META) {        for (var i = 0; i < mp.module_paths.length; i++) {            try {                var f = mp.utils.join_path(mp.module_paths[i], rest);                mp.utils.read_file(f, 1);  // throws on any error                return f;            } catch (e) {}        }        throw(Error("Cannot find module file '" + rest + "'"));    }     return id + ".js";}

Lines 1–500 of 813.

Keyboard shortcuts

General

Show keyboard shortcuts
?
Focus the current search
/

Issues and pull requests

Open filters
f

Issues, pull requests and Inbox

Select next item
j↓
Select previous item
k↑
Open selected item
Enter
Dismiss filters or clear selection
Esc

Repository code

Go to file
t