Implements JS with almost identical API to the Lua support. Key differences from Lua: - The global mp, mp.msg and mp.utils are always available. - Instead of returning x, error, return x and expose mp.last_error(). - Timers are JS standard set/clear Timeout/Interval. - Supports CommonJS modules/require. - Added at mp.utils: getenv, read_file, write_file and few more. - Global print and dump (expand objects) functions. - mp.options currently not supported. See DOCS/man/javascript.rst for more details.
Showing the changes to player/javascript (1 of 8 files). Show all
Unified · Split
| @@ -0,0 +1,495 @@ | |||
| 1 | "use strict"; | ||
| 2 | (function main_default_js(g) { | ||
| 3 | // - g is the global object. | ||
| 4 | // - User callbacks called without 'this', global only if callee is non-strict. | ||
| 5 | // - The names of function expressions are not required, but are used in stack | ||
| 6 | // traces. We name them where useful to show up (fname:#line always shows). | ||
| 7 | |||
| 8 | mp.msg = { log: mp.log }; | ||
| 9 | mp.msg.verbose = mp.log.bind(null, "v"); | ||
| 10 | var levels = ["fatal", "error", "warn", "info", "debug"]; | ||
| 11 | levels.forEach(function(l) { mp.msg[l] = mp.log.bind(null, l) }); | ||
| 12 | |||
| 13 | // same as {} but without inherited stuff, e.g. o["toString"] doesn't exist. | ||
| 14 | // used where we try to fetch items by keys which we don't absolutely trust. | ||
| 15 | function new_cache() { | ||
| 16 | return Object.create(null, {}); | ||
| 17 | } | ||
| 18 | |||
| 19 | /********************************************************************** | ||
| 20 | * event handlers, property observers, client messages, hooks | ||
| 21 | *********************************************************************/ | ||
| 22 | var ehandlers = new_cache() // items of event-name: array of {maybe cb: fn} | ||
| 23 | |||
| 24 | mp.register_event = function(name, fn) { | ||
| 25 | if (!ehandlers[name]) | ||
| 26 | ehandlers[name] = []; | ||
| 27 | ehandlers[name] = ehandlers[name].concat([{cb: fn}]); // replaces the arr | ||
| 28 | return mp._request_event(name, true); | ||
| 29 | } | ||
| 30 | |||
| 31 | mp.unregister_event = function(fn) { | ||
| 32 | for (var name in ehandlers) { | ||
| 33 | ehandlers[name] = ehandlers[name].filter(function(h) { | ||
| 34 | if (h.cb != fn) | ||
| 35 | return true; | ||
| 36 | delete h.cb; // dispatch could have a ref to h | ||
| 37 | }); // replacing, not mutating the array | ||
| 38 | if (!ehandlers[name].length) { | ||
| 39 | delete ehandlers[name]; | ||
| 40 | mp._request_event(name, false); | ||
| 41 | } | ||
| 42 | } | ||
| 43 | } | ||
| 44 | |||
| 45 | // call only pre-registered handlers, but not ones which got unregistered | ||
| 46 | function dispatch_event(e) { | ||
| 47 | var handlers = ehandlers[e.event]; | ||
| 48 | if (handlers) { | ||
| 49 | for (var len = handlers.length, i = 0; i < len; i++) { | ||
| 50 | var cb = handlers[i].cb; // 'handlers' won't mutate, but unregister | ||
| 51 | if (cb) // could remove cb from some items | ||
| 52 | cb(e); | ||
| 53 | } | ||
| 54 | } | ||
| 55 | } | ||
| 56 | |||
| 57 | // ----- property observers ----- | ||
| 58 | var next_oid = 1, | ||
| 59 | observers = new_cache(); // items of id: fn | ||
| 60 | |||
| 61 | mp.observe_property = function(name, format, fn) { | ||
| 62 | var id = next_oid++; | ||
| 63 | observers[id] = fn; | ||
| 64 | return mp._observe_property(id, name, format || undefined); // allow null | ||
| 65 | } | ||
| 66 | |||
| 67 | mp.unobserve_property = function(fn) { | ||
| 68 | for (var id in observers) { | ||
| 69 | if (observers[id] == fn) { | ||
| 70 | delete observers[id]; | ||
| 71 | mp._unobserve_property(id); | ||
| 72 | } | ||
| 73 | } | ||
| 74 | } | ||
| 75 | |||
| 76 | function notify_observer(e) { | ||
| 77 | var cb = observers[e.id]; | ||
| 78 | if (cb) | ||
| 79 | cb(e.name, e.data); | ||
| 80 | } | ||
| 81 | |||
| 82 | // ----- Client messages ----- | ||
| 83 | var messages = new_cache(); // items of name: fn | ||
| 84 | |||
| 85 | // overrides name. no libmpv API to reg/unreg specific messages. | ||
| 86 | mp.register_script_message = function(name, fn) { | ||
| 87 | messages[name] = fn; | ||
| 88 | } | ||
| 89 | |||
| 90 | mp.unregister_script_message = function(name) { | ||
| 91 | delete messages[name]; | ||
| 92 | } | ||
| 93 | |||
| 94 | function dispatch_message(ev) { | ||
| 95 | var cb = ev.args.length ? messages[ev.args[0]] : false; | ||
| 96 | if (cb) | ||
| 97 | cb.apply(null, ev.args.slice(1)); | ||
| 98 | } | ||
| 99 | |||
| 100 | // ----- hooks ----- | ||
| 101 | var next_hid = 1, | ||
| 102 | hooks = new_cache(); // items of id: fn | ||
| 103 | |||
| 104 | function hook_run(id, cont) { | ||
| 105 | var cb = hooks[id]; | ||
| 106 | if (cb) | ||
| 107 | cb(); | ||
| 108 | mp.commandv("hook-ack", cont); | ||
| 109 | } | ||
| 110 | |||
| 111 | mp.add_hook = function add_hook(name, pri, fn) { | ||
| 112 | if (next_hid == 1) // doesn't really matter if we do it once or always | ||
| 113 | mp.register_script_message("hook_run", hook_run); | ||
| 114 | var id = next_hid++; | ||
| 115 | hooks[id] = fn; | ||
| 116 | return mp.commandv("hook-add", name, id, pri); | ||
| 117 | } | ||
| 118 | |||
| 119 | /********************************************************************** | ||
| 120 | * key bindings | ||
| 121 | *********************************************************************/ | ||
| 122 | // binds: items of (binding) name which are objects of: | ||
| 123 | // {cb: fn, forced: bool, maybe input: str, repeatable: bool, complex: bool} | ||
| 124 | var binds = new_cache(); | ||
| 125 | |||
| 126 | function dispatch_key_binding(name, state) { | ||
| 127 | var cb = binds[name] ? binds[name].cb : false; | ||
| 128 | if (cb) // "script-binding [<script_name>/]<name>" command was invoked | ||
| 129 | cb(state); | ||
| 130 | } | ||
| 131 | |||
| 132 | function update_input_sections() { | ||
| 133 | var def = [], forced = []; | ||
| 134 | for (var n in binds) // Array.join() will later skip undefined .input | ||
| 135 | (binds[n].forced ? forced : def).push(binds[n].input); | ||
| 136 | |||
| 137 | var sect = "input_" + mp.script_name; | ||
| 138 | mp.commandv("define-section", sect, def.join("\n"), "default"); | ||
| 139 | mp.commandv("enable-section", sect, "allow-hide-cursor+allow-vo-dragging"); | ||
| 140 | |||
| 141 | sect = "input_forced_" + mp.script_name; | ||
| 142 | mp.commandv("define-section", sect, forced.join("\n"), "force"); | ||
| 143 | mp.commandv("enable-section", sect, "allow-hide-cursor+allow-vo-dragging"); | ||
| 144 | } | ||
| 145 | |||
| 146 | // name/opts maybe omitted. opts: object with optional bool members: repeatable, | ||
| 147 | // complex, forced, or a string str which is evaluated as object {str: true}. | ||
| 148 | var next_bid = 1; | ||
| 149 | function add_binding(forced, key, name, fn, opts) { | ||
| 150 | if (typeof name == "function") { // as if "name" is not part of the args | ||
| 151 | opts = fn; | ||
| 152 | fn = name; | ||
| 153 | name = "__keybinding" + next_bid++; // new unique binding name | ||
| 154 | } | ||
| 155 | var key_data = {forced: forced}; | ||
| 156 | switch (typeof opts) { // merge opts into key_data | ||
| 157 | case "string": key_data[opts] = true; break; | ||
| 158 | case "object": for (var o in opts) key_data[o] = opts[o]; | ||
| 159 | } | ||
| 160 | |||
| 161 | if (key_data.complex) { | ||
| 162 | mp.register_script_message(name, function msg_cb() { | ||
| 163 | fn({event: "press", is_mouse: false}); | ||
| 164 | }); | ||
| 165 | var KEY_STATES = { u: "up", d: "down", r: "repeat", p: "press" }; | ||
| 166 | key_data.cb = function key_cb(state) { | ||
| 167 | fn({ | ||
| 168 | event: KEY_STATES[state[0]] || "unknown", | ||
| 169 | is_mouse: state[1] == "m" | ||
| 170 | }); | ||
| 171 | } | ||
| 172 | } else { | ||
| 173 | mp.register_script_message(name, fn); | ||
| 174 | key_data.cb = function key_cb(state) { | ||
| 175 | // Emulate the semantics at input.c: mouse emits on up, kb on down. | ||
| 176 | // Also, key repeat triggers the binding again. | ||
| 177 | var e = state[0], | ||
| 178 | emit = (state[1] == "m") ? (e == "u") : (e == "d"); | ||
| 179 | if (emit || e == "p" || e == "r" && key_data.repeatable) | ||
| 180 | fn(); | ||
| 181 | } | ||
| 182 | } | ||
| 183 | |||
| 184 | if (key) | ||
| 185 | key_data.input = key + " script-binding " + mp.script_name + "/" + name; | ||
| 186 | binds[name] = key_data; // used by user and/or our (key) script-binding | ||
| 187 | update_input_sections(); | ||
| 188 | } | ||
| 189 | |||
| 190 | mp.add_key_binding = add_binding.bind(null, false); | ||
| 191 | mp.add_forced_key_binding = add_binding.bind(null, true); | ||
| 192 | |||
| 193 | mp.remove_key_binding = function(name) { | ||
| 194 | mp.unregister_script_message(name); | ||
| 195 | delete binds[name]; | ||
| 196 | update_input_sections(); | ||
| 197 | } | ||
| 198 | |||
| 199 | /********************************************************************** | ||
| 200 | Timers: compatible HTML5 WindowTimers - set/clear Timeout/Interval | ||
| 201 | - Spec: https://www.w3.org/TR/html5/webappapis.html#timers | ||
| 202 | - Guaranteed to callback a-sync to [re-]insertion (event-loop wise). | ||
| 203 | - Guaranteed to callback by expiration order, or, if equal, by insertion order. | ||
| 204 | - Not guaranteed schedule accuracy, though intervals should have good average. | ||
| 205 | *********************************************************************/ | ||
| 206 | |||
| 207 | // pending 'timers' ordered by expiration: latest at index 0 (top fires first). | ||
| 208 | // Earlier timers are quicker to handle - just push/pop or fewer items to shift. | ||
| 209 | var next_tid = 1, | ||
| 210 | timers = [], // while in process_timers, just insertion-ordered (push) | ||
| 211 | tset_is_push = false, // signal set_timer that we're in process_timers | ||
| 212 | tcanceled = false, // or object of items timer-id: true | ||
| 213 | now = mp.get_time_ms; // just an alias | ||
| 214 | |||
| 215 | function insert_sorted(arr, t) { | ||
| 216 | for (var i = arr.length - 1; i >= 0 && t.when >= arr[i].when; i--) | ||
| 217 | arr[i + 1] = arr[i]; // move up timers which fire earlier than t | ||
| 218 | arr[i + 1] = t; // i is -1 or fires later than t | ||
| 219 | } | ||
| 220 | |||
| 221 | // args (is "arguments"): fn_or_str [,duration [,user_arg1 [, user_arg2 ...]]] | ||
| 222 | function set_timer(repeat, args) { | ||
| 223 | var fos = args[0], | ||
| 224 | duration = Math.max(0, (args[1] || 0)), // minimum and default are 0 | ||
| 225 | t = { | ||
| 226 | id: next_tid++, | ||
| 227 | when: now() + duration, | ||
| 228 | interval: repeat ? duration : -1, | ||
| 229 | callback: (typeof fos == "function") ? fos : Function(fos), | ||
| 230 | args: (args.length < 3) ? false : [].slice.call(args, 2), | ||
| 231 | }; | ||
| 232 | |||
| 233 | if (tset_is_push) { | ||
| 234 | timers.push(t); | ||
| 235 | } else { | ||
| 236 | insert_sorted(timers, t); | ||
| 237 | } | ||
| 238 | return t.id; | ||
| 239 | } | ||
| 240 | |||
| 241 | g.setTimeout = function setTimeout() { return set_timer(false, arguments) }; | ||
| 242 | g.setInterval = function setInterval() { return set_timer(true, arguments) }; | ||
| 243 | |||
| 244 | g.clearTimeout = g.clearInterval = function(id) { | ||
| 245 | if (id < next_tid) { // must ignore if not active timer id. | ||
| 246 | if (!tcanceled) | ||
| 247 | tcanceled = {}; | ||
| 248 | tcanceled[id] = true; | ||
| 249 | } | ||
| 250 | } | ||
| 251 | |||
| 252 | // arr: ordered timers array. ret: -1: no timers, 0: due, positive: ms to wait | ||
| 253 | function peek_wait(arr) { | ||
| 254 | return arr.length ? Math.max(0, arr[arr.length - 1].when - now()) : -1; | ||
| 255 | } | ||
| 256 | |||
| 257 | // Callback all due non-canceled timers which were inserted before calling us. | ||
| 258 | // Returns wait in ms till the next timer (possibly 0), or -1 if nothing pends. | ||
| 259 | function process_timers() { | ||
| 260 | var wait = peek_wait(timers); | ||
| 261 | if (wait != 0) | ||
| 262 | return wait; | ||
| 263 | |||
| 264 | var actives = timers; // only process those already inserted by now | ||
| 265 | timers = []; // we'll handle added new timers at the end of processing. | ||
| 266 | tset_is_push = true; // signal set_timer to just push-insert | ||
| 267 | |||
| 268 | do { | ||
| 269 | var t = actives.pop(); | ||
| 270 | if (tcanceled && tcanceled[t.id]) | ||
| 271 | continue; | ||
| 272 | |||
| 273 | if (t.args) { | ||
| 274 | t.callback.apply(null, t.args); | ||
| 275 | } else { | ||
| 276 | (0, t.callback)(); // faster, nicer stack trace than t.cb.call() | ||
| 277 | } | ||
| 278 | |||
| 279 | if (t.interval >= 0) { | ||
| 280 | // allow 20 ms delay/clock-resolution/gc before we skip and reset | ||
| 281 | t.when = Math.max(now() - 20, t.when + t.interval); | ||
| 282 | timers.push(t); // insertion order only | ||
| 283 | } | ||
| 284 | } while (peek_wait(actives) == 0); | ||
| 285 | |||
| 286 | // new 'timers' are insertion-ordered. remains of actives are fully ordered | ||
| 287 | timers.forEach(function(t) { insert_sorted(actives, t) }); | ||
| 288 | timers = actives; // now we're fully ordered again, and with all timers | ||
| 289 | tset_is_push = false; | ||
| 290 | if (tcanceled) { | ||
| 291 | timers = timers.filter(function(t) { return !tcanceled[t.id] }); | ||
| 292 | tcanceled = false; | ||
| 293 | } | ||
| 294 | return peek_wait(timers); | ||
| 295 | } | ||
| 296 | |||
| 297 | /********************************************************************** | ||
| 298 | CommonJS module/require | ||
| 299 | |||
| 300 | Spec: http://wiki.commonjs.org/wiki/Modules/1.1.1 | ||
| 301 | - All the mandatory requirements are implemented, all the unit tests pass. | ||
| 302 | - The implementation makes the following exception: | ||
| 303 | - Allows the chars [~@:\\] in module id for meta-dir/builtin/dos-drive/UNC. | ||
| 304 | |||
| 305 | Implementation choices beyond the specification: | ||
| 306 | - A module may assign to module.exports (rather than only to exports). | ||
| 307 | - A module's 'this' is the global object, also if it sets strict mode. | ||
| 308 | - No 'global'/'self'. Users can do "this.global = this;" before require(..) | ||
| 309 | - A module has "privacy of its top scope", runs in its own function context. | ||
| 310 | - No id identity with symlinks - a valid choice which others make too. | ||
| 311 | - require("X") always maps to "X.js" -> require("foo.js") is file "foo.js.js". | ||
| 312 | - Global modules search paths are 'scripts/modules.js/' in mpv config dirs. | ||
| 313 | - A main script could e.g. require("./abc") to load a non-global module. | ||
| 314 | - Module id supports mpv path enhancements, e.g. ~/foo, ~~/bar, ~~desktop/baz | ||
| 315 | *********************************************************************/ | ||
| 316 | |||
| 317 | // Internal meta top-dirs. Users should not rely on these names. | ||
| 318 | var MODULES_META = "~~modules", | ||
| 319 | SCRIPTDIR_META = "~~scriptdir", // relative script path -> meta absolute id | ||
| 320 | main_script = mp.utils.split_path(mp.script_file); // -> [ path, file ] | ||
| 321 | |||
| 322 | function resolve_module_file(id) { | ||
| 323 | var sep = id.indexOf("/"), | ||
| 324 | base = id.substring(0, sep), | ||
| 325 | rest = id.substring(sep + 1) + ".js"; | ||
| 326 | |||
| 327 | if (base == SCRIPTDIR_META) | ||
| 328 | return mp.utils.join_path(main_script[0], rest); | ||
| 329 | |||
| 330 | if (base == MODULES_META) { | ||
| 331 | var path = mp.find_config_file("scripts/modules.js/" + rest); | ||
| 332 | if (!path) | ||
| 333 | throw(Error("Cannot find module file '" + rest + "'")); | ||
| 334 | return path; | ||
| 335 | } | ||
| 336 | |||
| 337 | return id + ".js"; | ||
| 338 | } | ||
| 339 | |||
| 340 | // Delimiter '/', remove redundancies, prefix with modules meta-root if needed. | ||
| 341 | // E.g. c:\x -> c:/x, or ./x//y/../z -> ./x/z, or utils/x -> ~~modules/utils/x . | ||
| 342 | function canonicalize(id) { | ||
| 343 | var path = id.replace(/\\/g,"/").split("/"), | ||
| 344 | t = path[0], | ||
| 345 | base = []; | ||
| 346 | |||
| 347 | // if not strictly relative then must be top-level. figure out base/rest | ||
| 348 | if (t != "." && t != "..") { | ||
| 349 | // global module if it's not fs-root/home/dos-drive/builtin/meta-dir | ||
| 350 | if (!(t == "" || t == "~" || t[1] == ":" || t == "@" || t.match(/^~~/))) | ||
| 351 | path.unshift(MODULES_META); // add an explicit modules meta-root | ||
| 352 | |||
| 353 | if (id.match(/^\\\\/)) // simple UNC handling, preserve leading \\srv | ||
| 354 | path = ["\\\\" + path[2]].concat(path.slice(3)); // [ \\srv, shr..] | ||
| 355 | |||
| 356 | if (t[1] == ":" && t.length > 2) { // path: [ "c:relative", "path" ] | ||
| 357 | path[0] = t.substring(2); | ||
| 358 | path.unshift(t[0] + ":."); // -> [ "c:.", "relative", "path" ] | ||
| 359 | } | ||
| 360 | base = [path.shift()]; | ||
| 361 | } | ||
| 362 | |||
| 363 | // path is now logically relative. base, if not empty, is its [meta] root. | ||
| 364 | // normalize the relative part - always id-based (spec Module Id, 1.3.6). | ||
| 365 | var cr = []; // canonicalized relative | ||
| 366 | for (var i = 0; i < path.length; i++) { | ||
| 367 | if (path[i] == "." || path[i] == "") | ||
| 368 | continue; | ||
| 369 | if (path[i] == ".." && cr.length && cr[cr.length - 1] != "..") { | ||
| 370 | cr.pop(); | ||
| 371 | continue; | ||
| 372 | } | ||
| 373 | cr.push(path[i]); | ||
| 374 | } | ||
| 375 | |||
| 376 | if (!base.length && cr[0] != "..") | ||
| 377 | base = ["."]; // relative and not ../<stuff> so must start with ./ | ||
| 378 | return base.concat(cr).join("/"); | ||
| 379 | } | ||
| 380 | |||
| 381 | function resolve_module_id(base_id, new_id) { | ||
| 382 | new_id = canonicalize(new_id); | ||
| 383 | if (!new_id.match(/^\.\/|^\.\.\//)) // doesn't start with ./ or ../ | ||
| 384 | return new_id; // not relative, we don't care about base_id | ||
| 385 | |||
| 386 | var combined = mp.utils.join_path(mp.utils.split_path(base_id)[0], new_id); | ||
| 387 | return canonicalize(combined); | ||
| 388 | } | ||
| 389 | |||
| 390 | var req_cache = new_cache(); // global for all instances of require | ||
| 391 | |||
| 392 | // ret: a require function instance which uses base_id to resolve relative id's | ||
| 393 | function new_require(base_id) { | ||
| 394 | return function require(id) { | ||
| 395 | id = resolve_module_id(base_id, id); // id is now top-level | ||
| 396 | if (req_cache[id]) | ||
| 397 | return req_cache[id].exports; | ||
| 398 | |||
| 399 | var new_module = {id: id, exports: {}}; | ||
| 400 | req_cache[id] = new_module; | ||
| 401 | try { | ||
| 402 | var filename = resolve_module_file(id); | ||
| 403 | // we need dedicated free vars + filename in traces + allow strict | ||
| 404 | var str = "mp._req = function(require, exports, module) {" + | ||
| 405 | mp.utils.read_file(filename) + | ||
| 406 | "\n;}"; | ||
| 407 | mp.utils.compile_js(filename, str)(); // only runs the assignment | ||
| 408 | var tmp = mp._req; // we have mp._req, or else we'd have thrown | ||
| 409 | delete mp._req; | ||
| 410 | tmp.call(g, new_require(id), new_module.exports, new_module); | ||
| 411 | } catch (e) { | ||
| 412 | delete req_cache[id]; | ||
| 413 | throw(e); | ||
| 414 | } | ||
| 415 | |||
| 416 | return new_module.exports; | ||
| 417 | }; | ||
| 418 | } | ||
| 419 | |||
| 420 | g.require = new_require(SCRIPTDIR_META + "/" + main_script[1]); | ||
| 421 | |||
| 422 | /********************************************************************** | ||
| 423 | * various | ||
| 424 | *********************************************************************/ | ||
| 425 | g.print = mp.msg.info; // convenient alias | ||
| 426 | mp.get_script_name = function() { return mp.script_name }; | ||
| 427 | mp.get_script_file = function() { return mp.script_file }; | ||
| 428 | mp.get_time = function() { return mp.get_time_ms() / 1000 }; | ||
| 429 | mp.utils.getcwd = function() { return mp.get_property("working-directory") }; | ||
| 430 | mp.dispatch_event = dispatch_event; | ||
| 431 | mp.process_timers = process_timers; | ||
| 432 | |||
| 433 | mp.get_opt = function(key, def) { | ||
| 434 | var v = mp.get_property_native("options/script-opts")[key]; | ||
| 435 | return (typeof v != "undefined") ? v : def; | ||
| 436 | } | ||
| 437 | |||
| 438 | mp.osd_message = function osd_message(text, duration) { | ||
| 439 | mp.commandv("show_text", text, Math.round(1000 * (duration || -1))); | ||
| 440 | } | ||
| 441 | |||
| 442 | // ----- dump: like print, but expands objects/arrays recursively ----- | ||
| 443 | function replacer(k, v) { | ||
| 444 | var t = typeof v; | ||
| 445 | if (t == "function" || t == "undefined") | ||
| 446 | return "<" + t + ">"; | ||
| 447 | if (Array.isArray(this) && t == "object" && v !== null) { // "safe" mode | ||
| 448 | if (this.indexOf(v) >= 0) | ||
| 449 | return "<VISITED>"; | ||
| 450 | this.push(v); | ||
| 451 | } | ||
| 452 | return v; | ||
| 453 | } | ||
| 454 | |||
| 455 | function obj2str(v) { | ||
| 456 | try { // can process objects more than once, but throws on cycles | ||
| 457 | return JSON.stringify(v, replacer, 2); | ||
| 458 | } catch (e) { // simple safe: exclude visited objects, even if not cyclic | ||
| 459 | return JSON.stringify(v, replacer.bind([]), 2); | ||
| 460 | } | ||
| 461 | } | ||
| 462 | |||
| 463 | g.dump = function dump() { | ||
| 464 | var toprint = []; | ||
| 465 | for (var i = 0; i < arguments.length; i++) { | ||
| 466 | var v = arguments[i]; | ||
| 467 | toprint.push((typeof v == "object") ? obj2str(v) : replacer(0, v)); | ||
| 468 | } | ||
| 469 | print.apply(null, toprint); | ||
| 470 | } | ||
| 471 | |||
| 472 | /********************************************************************** | ||
| 473 | * main listeners and event loop | ||
| 474 | *********************************************************************/ | ||
| 475 | mp.keep_running = true; | ||
| 476 | g.exit = function() { mp.keep_running = false }; // user-facing too | ||
| 477 | mp.register_event("shutdown", g.exit); | ||
| 478 | mp.register_event("property-change", notify_observer); | ||
| 479 | mp.register_event("client-message", dispatch_message); | ||
| 480 | mp.register_script_message("key-binding", dispatch_key_binding); | ||
| 481 | |||
| 482 | g.mp_event_loop = function mp_event_loop() { | ||
| 483 | var wait = 0; // seconds | ||
| 484 | do { // distapch events as long as they arrive, then do the timers | ||
| 485 | var e = mp.wait_event(wait); | ||
| 486 | if (e.event != "none") { | ||
| 487 | dispatch_event(e); | ||
| 488 | wait = 0; // poll the next one | ||
| 489 | } else { | ||
| 490 | wait = process_timers() / 1000; | ||
| 491 | } | ||
| 492 | } while (mp.keep_running); | ||
| 493 | }; | ||
| 494 | |||
| 495 | })(this) | ||