| /* | |
| * GPAC - Multimedia Framework C SDK | |
| * | |
| * Authors: Jean Le Feuvre | |
| * Copyright (c) Telecom ParisTech 2020-2025 | |
| * All rights reserved | |
| * | |
| * This file is part of GPAC / vout default ui | |
| * | |
| * GPAC 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, or (at your option) | |
| * any later version. | |
| * | |
| * GPAC 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 this library; see the file COPYING. If not, write to | |
| * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. | |
| * | |
| */ | |
| /*very simple controller for vout and vout+aout allowing stats display and playback control */ | |
| import * as evg from 'evg' | |
| import { Sys as sys } from 'gpaccore' | |
| _gpac_log_name=""; | |
| print("Type 'h' in window for command list"); | |
| let aout = null; | |
| let vout = null; | |
| let speed = 1; | |
| let win_id = 0; | |
| let drop=0; | |
| let fullscreen = false; | |
| let paused=0; | |
| let duration=-1; | |
| let codec_slow=false; | |
| let audio_only = false; | |
| let ol_buffer = null; | |
| let ol_canvas = null; | |
| let ol_width = 200; | |
| let ol_height = 20; | |
| let text = new evg.Text(); | |
| text.font = 'SERIF'; | |
| text.fontsize = 20; | |
| text.baseline = GF_TEXT_BASELINE_HANGING; | |
| text.align = GF_TEXT_ALIGN_LEFT; | |
| text.lineSpacing = 20; | |
| const OL_NONE = 0; | |
| const OL_HELP = 1; | |
| const OL_STATS = 2; | |
| const OL_PLAY = 3; | |
| const OL_AUTH = 4; | |
| let overlay_type = OL_NONE; | |
| let overlay_type_bck = OL_NONE; | |
| let brush = new evg.SolidBrush(); | |
| brush.set_color('white'); | |
| let disp_size = null; | |
| let task_reschedule = 500; | |
| let rot = 0; | |
| let flip = 0; | |
| let use_libcaca=false; | |
| let do_coverage=0; | |
| if (sys.get_opt('temp', 'vout_cov') == 'yes') do_coverage = 1; | |
| function check_vout() | |
| { | |
| let drv = vout.get_arg('drv'); | |
| if (!drv) drv = sys.get_opt('core', 'video-output'); | |
| if (drv == 'caca') { | |
| session.set_auth_fun( null ); | |
| use_libcaca=true; | |
| } | |
| speed = vout.get_arg('speed'); | |
| drop = vout.get_arg('drop'); | |
| fullscreen = vout.get_arg('fullscreen'); | |
| win_id = vout.get_arg('wid'); | |
| } | |
| if (parent_filter && parent_filter.type=='vout') { | |
| vout = parent_filter; | |
| check_vout(); | |
| } | |
| function check_filters() | |
| { | |
| if (aout && vout) return; | |
| let i; | |
| for (i=0; i< session.nb_filters; i++) { | |
| let f = session.get_filter(i); | |
| if (!aout && (f.type==="aout")) aout = f; | |
| else if (!vout && (f.type==="vout")) { | |
| vout = f; | |
| check_vout(); | |
| } | |
| } | |
| } | |
| check_filters(); | |
| function setup_overlay() | |
| { | |
| let target_width=ol_width; | |
| let target_height=ol_height; | |
| if (vout.nb_ipid) audio_only=false; | |
| disp_size = vout.get_arg('owsize'); | |
| if (disp_size==null) return false; | |
| if (audio_only && !disp_size.x) { | |
| disp_size = {"x": 320, "y": 80}; | |
| } | |
| task_reschedule = 500; | |
| switch (overlay_type) { | |
| case OL_HELP: | |
| target_width = Math.floor(disp_size.x/2); | |
| target_height = Math.floor( (shortcuts.length+2) * 30); | |
| break; | |
| case OL_PLAY: | |
| target_width = Math.floor(disp_size.x/2); | |
| target_height = Math.floor( 60); | |
| task_reschedule = 500; | |
| break; | |
| case OL_STATS: | |
| target_width = Math.floor(disp_size.x/2); | |
| target_height = Math.floor(disp_size.y/2); | |
| task_reschedule = 250; | |
| break; | |
| case OL_AUTH: | |
| target_width = Math.floor(4*disp_size.x/5); | |
| target_height = Math.floor(4*disp_size.y/5); | |
| task_reschedule = 100; | |
| break; | |
| default: | |
| if (!ol_width) | |
| ol_width = Math.floor(disp_size.x/3); | |
| if (!ol_height) | |
| ol_height = Math.floor(disp_size.y/4); | |
| break; | |
| } | |
| init_wnd=true; | |
| if (audio_only || !vout.nb_ipid) { | |
| target_width = ol_width = disp_size.x; | |
| target_height = ol_height = disp_size.y; | |
| } | |
| while (target_width%2) target_width--; | |
| while (target_height%2) target_height--; | |
| if ((ol_width!=target_width) || (ol_height!=target_height)) { | |
| ol_buffer = null; | |
| ol_width = target_width; | |
| ol_height = target_height; | |
| } | |
| if (use_libcaca) return true; | |
| if (ol_buffer != null) return true; | |
| ol_buffer = new ArrayBuffer(ol_width*ol_height*4); | |
| ol_canvas = new evg.Canvas(ol_width, ol_height, 'rgba', ol_buffer); | |
| ol_canvas.clearf(0.2, 0.2, 0.2, 0.6); | |
| return true; | |
| } | |
| session.post_task( () => { | |
| if (session.last_task) { | |
| return false; | |
| } | |
| check_filters(); | |
| if (!vout) return 1000; | |
| if (vout.nb_ipid) { | |
| audio_only=false; | |
| return false; | |
| } | |
| if (aout) { | |
| if (!aout.nb_ipid || !session.connected) { | |
| return 100; | |
| } | |
| if (!audio_only) { | |
| overlay_type=OL_PLAY; | |
| audio_only=true; | |
| toggle_overlay(); | |
| } | |
| } | |
| return false; | |
| }, "check_vout_pids"); | |
| let last_x=0, last_y=0; | |
| session.set_event_fun( (evt)=> { | |
| if (evt.type != GF_FEVT_USER) return 0; | |
| if ((evt.ui_type == GF_EVENT_CODEC_SLOW) || (evt.ui_type == GF_EVENT_CODEC_OK)) { | |
| codec_slow = (evt.ui_type == GF_EVENT_CODEC_SLOW) ? true : false; | |
| if (overlay_type==OL_AUTH) return 0; | |
| ol_visible = false; | |
| overlay_type=OL_PLAY; | |
| toggle_overlay(); | |
| update_play(); | |
| return false; | |
| } | |
| try { | |
| if(evt.window != win_id) return true; | |
| } catch (e) { | |
| return true; | |
| } | |
| switch (evt.ui_type) { | |
| case GF_EVENT_MOUSEDOWN: | |
| if (use_libcaca) return GF_FALSE; | |
| last_x = evt.mouse_x; | |
| last_y = evt.mouse_y; | |
| break; | |
| case GF_EVENT_MOUSEUP: | |
| if (use_libcaca) return GF_FALSE; | |
| if ((last_x != evt.mouse_x) || (last_y != evt.mouse_y)) return 0; | |
| if (overlay_type==OL_AUTH) return 0; | |
| if (overlay_type==OL_PLAY) { | |
| if (prog_interact(evt)) return 0; | |
| } | |
| if (ol_visible) { | |
| toggle_overlay(); | |
| return true; | |
| } else if (!audio_only) { | |
| overlay_type=OL_PLAY; | |
| toggle_overlay(); | |
| return true; | |
| } | |
| break; | |
| case GF_EVENT_KEYDOWN: | |
| return process_keyboard(evt); | |
| case GF_EVENT_TEXTINPUT: | |
| if (overlay_type==OL_AUTH) { | |
| if (auth_state==0) { | |
| if ((evt.char == '\n') || (evt.char == '\r')) | |
| auth_state = 1; | |
| else if (evt.char == '\b') | |
| auth_name = auth_name.slice(0, auth_name.length - 1); | |
| else | |
| auth_name += ''+evt.char; | |
| } else if (auth_state==1) { | |
| if ((evt.char == '\n') || (evt.char == '\r')) { | |
| auth_state = 2; | |
| auth_requests[0].done(auth_name, auth_pass); | |
| auth_name = ''; | |
| auth_pass = ''; | |
| auth_requests.shift(); | |
| if (auth_requests.length) { | |
| auth_state = 0; | |
| } else { | |
| auth_state = 0; | |
| toggle_overlay(); | |
| text.font = 'SERIF'; | |
| text.fontsize = 20; | |
| text.baseline = GF_TEXT_BASELINE_HANGING; | |
| text.align = GF_TEXT_ALIGN_LEFT; | |
| text.lineSpacing = 20; | |
| overlay_type = overlay_type_bck; | |
| if (overlay_type) toggle_overlay(); | |
| } | |
| } else if (evt.char == '\b') | |
| auth_pass = auth_pass.slice(0, auth_pass.length - 1); | |
| else | |
| auth_pass += ''+evt.char; | |
| } | |
| auth_modified = true; | |
| } | |
| return false; | |
| case GF_EVENT_SIZE: | |
| if (do_coverage) { | |
| overlay_type=OL_PLAY; | |
| ol_visible=true; | |
| if (do_coverage==1) { | |
| do_coverage = 0; | |
| coverage_tests(); | |
| } | |
| } | |
| if (ol_visible) { | |
| let old_type = overlay_type; | |
| vout.lock(true); | |
| toggle_overlay(); | |
| overlay_type = old_type; | |
| toggle_overlay(); | |
| vout.update('oldata', ol_buffer); | |
| vout.lock(false); | |
| } | |
| break; | |
| default: | |
| } | |
| return false; | |
| } | |
| ); | |
| function coverage_tests() | |
| { | |
| print('coverage tests'); | |
| if (aout) aout.update('speed', '1'); | |
| let evt = new FilterEvent(GF_FEVT_QUALITY_SWITCH); | |
| evt.up = true; | |
| let src = vout.ipid_source(0); | |
| session.fire_event(evt, src); | |
| evt.up = false; | |
| session.fire_event(evt, src); | |
| } | |
| let interactive_scene=0; | |
| function check_duration() | |
| { | |
| if (duration!=-1) return (duration>=0) ? true : false; | |
| let dur=null; | |
| if (audio_only) { | |
| dur = aout.ipid_props(0, 'Duration'); | |
| interactive_scene = aout.ipid_props(0, 'InteractiveScene'); | |
| } else if (vout.nb_ipid) { | |
| dur = vout.ipid_props(0, 'Duration'); | |
| interactive_scene = vout.ipid_props(0, 'InteractiveScene'); | |
| } | |
| if (interactive_scene==1) { | |
| duration = 0; | |
| return; | |
| } | |
| if (dur==null) return; | |
| duration = dur.n; | |
| duration /= dur.d; | |
| return true; | |
| } | |
| function do_seek(val, mods, absolute) | |
| { | |
| if (! check_duration()) | |
| return; | |
| let seek_to=-1; | |
| if (!absolute) { | |
| if (mods & GF_KEY_MOD_CTRL) val *= 30; | |
| else if (mods & GF_KEY_MOD_SHIFT) val *= 10; | |
| seek_to = duration * val / 100; | |
| } | |
| //in seconds | |
| else { | |
| //30smin | |
| if (mods & GF_KEY_MOD_CTRL) val *= 1800; | |
| //10min | |
| else if (mods & GF_KEY_MOD_SHIFT) val *= 600; | |
| //30s | |
| else val *= 30; | |
| seek_to = val; | |
| } | |
| let last_ts_f = vout.last_ts_drop; | |
| if (last_ts_f==null) return; | |
| let last_ts = last_ts_f.n; | |
| last_ts /= last_ts_f.d; | |
| if (audio_only) | |
| last_ts -= aout.get_arg('media_offset'); | |
| else | |
| last_ts -= vout.get_arg('media_offset'); | |
| last_ts += seek_to; | |
| if (last_ts<0) last_ts = 0; | |
| else if (last_ts>duration) last_ts = duration-10; | |
| vout.update('start', ''+last_ts); | |
| if (aout) aout.update('start', '' + last_ts); | |
| } | |
| let init_wnd=false; | |
| function update_help() | |
| { | |
| let args = []; //['Shortcuts for vout', ' ']; | |
| shortcuts.forEach( (key, index) => { | |
| if (use_libcaca && (index>10)) | |
| return; | |
| args.push( key.code + ': ' + key.desc); | |
| }); | |
| if (audio_only) | |
| text.fontsize = 20; | |
| else | |
| text.fontsize = Math.floor(0.6*ol_height/args.length); | |
| text.set_text(args); | |
| if (use_libcaca) { | |
| let str = ''; | |
| args.forEach(a => { | |
| str+=''+a+'\n'; | |
| }); | |
| vout.update('oltxt', str); | |
| return; | |
| } | |
| //lock vout since we will modify data of the canvas | |
| vout.lock(true); | |
| if (init_wnd && audio_only) { | |
| let txtdim = text.measure(); | |
| ol_width = Math.floor(txtdim.width*1.2); | |
| while (ol_width%2) ol_width--; | |
| ol_height = Math.floor(txtdim.height*1.2); | |
| while (ol_height%2) ol_height--; | |
| ol_buffer = new ArrayBuffer(ol_width*ol_height*4); | |
| ol_canvas = new evg.Canvas(ol_width, ol_height, 'rgba', ol_buffer); | |
| vout.update('olsize', ''+ol_width+'x'+ol_height); | |
| vout.update('olwnd', '0x0x'+ol_width+'x'+ol_height); | |
| init_wnd=false; | |
| } | |
| ol_canvas.clearf(0.2, 0.2, 0.2, 0.6); | |
| let mx = new evg.Matrix2D(); | |
| mx.translate(-ol_width/2, ol_height/3); | |
| ol_canvas.matrix = mx; | |
| ol_canvas.path = text; | |
| ol_canvas.fill(brush); | |
| overlay_type=OL_NONE; | |
| vout.update('oldata', ol_buffer); | |
| vout.lock(false); | |
| } | |
| let progress_bar_path=null; | |
| let prog_ox=0; | |
| let prog_length=0; | |
| let reverse=false; | |
| function prog_interact(evt) | |
| { | |
| let ret = audio_only ? true : false; | |
| let x = evt.mouse_x - disp_size.x/2; | |
| let y = disp_size.y - evt.mouse_y - ol_height/2; | |
| if (x<-ol_width/2) return ret; | |
| if (x>ol_width/2) return ret; | |
| if (y>ol_height/2) return ret; | |
| if (!check_duration()) | |
| return true; | |
| if (!progress_bar_path.point_over(x, y)) { | |
| x -= prog_ox; | |
| x /= prog_length; | |
| x += 0.5; | |
| if (x>1) reverse = !reverse; | |
| return true; | |
| } | |
| x -= prog_ox; | |
| x /= prog_length; | |
| x += 0.5; | |
| x *= duration; | |
| if (audio_only) { | |
| aout.update('start', '' + x); | |
| } else { | |
| vout.update('start', '' + x); | |
| if (aout) aout.update('start', '' + x); | |
| } | |
| return true; | |
| } | |
| function update_play() | |
| { | |
| if (init_wnd) { | |
| let pos = '0x' + Math.floor(ol_height/2 - disp_size.y/2) + 'x'+ol_width+'x'+ol_height; | |
| vout.update('olwnd', pos); | |
| init_wnd=false; | |
| progress_bar_path=null; | |
| } | |
| if (!use_libcaca) | |
| ol_canvas.clearf(0.2, 0.2, 0.2, 0.6); | |
| let length = ol_width - 20; | |
| let last_ts_f = vout.last_ts_drop; | |
| if (!last_ts_f && aout) | |
| last_ts_f = aout.last_ts_drop; | |
| if (!last_ts_f) { | |
| let str = 'No input connected'; | |
| if (use_libcaca) { | |
| vout.update('oltxt', str); | |
| return; | |
| } | |
| text.fontsize = 20; | |
| text.set_text(str); | |
| let mx = new evg.Matrix2D(); | |
| mx.translate(- str.length * text.fontsize/3, -10); | |
| ol_canvas.matrix = mx; | |
| ol_canvas.path = text; | |
| ol_canvas.fill(brush); | |
| vout.update('oldata', ol_buffer); | |
| return; | |
| } | |
| let has_dur = check_duration(); | |
| if (!has_dur) reverse = false; | |
| let h, m, s; | |
| let last_ts = last_ts_f.n; | |
| last_ts /= last_ts_f.d; | |
| if (audio_only) | |
| last_ts -= aout.get_arg('media_offset'); | |
| else | |
| last_ts -= vout.get_arg('media_offset'); | |
| if (reverse) last_ts = duration - last_ts; | |
| h = Math.floor(last_ts/3600); | |
| m = Math.floor(last_ts/60 - h*60); | |
| s = Math.floor(last_ts - h*3600) - m*60; | |
| let str = reverse ? '-' : ' '; | |
| if (duration>=3600) { | |
| if (h<10) str+='0'; | |
| str+=h+':'; | |
| } | |
| if (m<10) str+='0'; | |
| str+=m+':'; | |
| if (s<10) str+='0'; | |
| str+=s; | |
| if (use_libcaca) { | |
| if (duration) { | |
| let progress = Math.ceil(100 * last_ts / duration); | |
| let i=0; | |
| str += ' '; | |
| while (i*10<progress) { | |
| str += '='; | |
| i++; | |
| } | |
| while (i<10) { | |
| str += '-'; | |
| i++; | |
| } | |
| str += ' ' + progress + ' %'; | |
| } | |
| vout.update('oltxt', str); | |
| return; | |
| } | |
| let fs = Math.floor(0.5*ol_height); | |
| if (fs > 60) fs=60; | |
| text.fontsize = fs; | |
| text.set_text(str); | |
| let mx = new evg.Matrix2D(); | |
| if (has_dur) | |
| mx.translate(ol_width/2 - str.length * text.fontsize/2, -10); | |
| else | |
| mx.translate(- str.length * text.fontsize/3, -10); | |
| ol_canvas.matrix = mx; | |
| ol_canvas.path = text; | |
| ol_canvas.fill(brush); | |
| length -= str.length * text.fontsize/2; | |
| if (has_dur) { | |
| if (!progress_bar_path) { | |
| prog_length = length; | |
| prog_ox = 10 + (length - ol_width) / 2; | |
| progress_bar_path = new evg.Path(); | |
| progress_bar_path.rectangle(prog_ox, 0, prog_length, 20, true); | |
| } | |
| ol_canvas.matrix = null; | |
| ol_canvas.path = progress_bar_path; | |
| brush.set_color('grey'); | |
| ol_canvas.fill(brush); | |
| let progress_path = new evg.Path(); | |
| let progress = last_ts / duration; | |
| brush.set_color('white'); | |
| progress_path.rectangle(prog_ox - prog_length/2, 9, prog_length*progress, 18, false); | |
| ol_canvas.path = progress_path; | |
| ol_canvas.fill(brush); | |
| } | |
| if (paused || (speed != 1) || codec_slow) { | |
| text.fontsize = 16; | |
| if (codec_slow) | |
| text.set_text('Codec Too Slow !'); | |
| else if (paused) | |
| text.set_text((paused==2) ? 'Step mode' : 'Paused'); | |
| else | |
| text.set_text('Speed: ' + speed); | |
| let mx = new evg.Matrix2D(); | |
| mx.translate(0, ol_height/4); | |
| ol_canvas.matrix = mx; | |
| ol_canvas.path = text; | |
| brush.set_color(codec_slow ? 'red' : 'cyan'); | |
| ol_canvas.fill(brush); | |
| brush.set_color('white'); | |
| } | |
| let src = vout ? vout.ipid_source(0) : aout.ipid_source(0); | |
| let hdr = null; | |
| if (src) { | |
| hdr = src.ipid_props(0, 'X-From-MABR'); | |
| if (!hdr) hdr = src.ipid_props(0, 'x-from-mabr'); | |
| } | |
| if (hdr) { | |
| text.fontsize = 16; | |
| let msg = 'MABR ' + ((hdr=='yes') ? 'on' : ((hdr=='no') ? 'off' : hdr)); | |
| text.set_text(msg); | |
| let mx = new evg.Matrix2D(); | |
| mx.translate(ol_width/2-10*msg.length, 5-ol_height/2); | |
| ol_canvas.matrix = mx; | |
| ol_canvas.path = text; | |
| brush.set_color((hdr=='yes') ? 'green' : 'red'); | |
| ol_canvas.fill(brush); | |
| brush.set_color('white'); | |
| } | |
| //we could lock vout to avoid any tearing ... | |
| vout.update('oldata', ol_buffer); | |
| } | |
| function build_graph(f, str, str_list, fdone) | |
| { | |
| let i, j, num_tiles=0; | |
| if (!f.nb_opid) { | |
| str += '->' + f.name; | |
| str_list.push(str); | |
| str = ''; | |
| fdone.push(f); | |
| return; | |
| } | |
| if (fdone.indexOf(f)>=0) { | |
| str += '->' + f.name; | |
| str_list.push(str); | |
| str = ''; | |
| return; | |
| } | |
| let len=0; | |
| if (!str.length) str = f.name; | |
| else str += '->' + f.name; | |
| text.set_text(str); | |
| len = text.measure().width; | |
| for (i=0; i<f.nb_opid; i++) { | |
| let sinks = f.opid_sinks(i); | |
| let sub_len=0; | |
| let sub_str; | |
| let p = f.opid_props(i, 'CodecID'); | |
| if (p == 'hvt1') { | |
| if (num_tiles) continue; | |
| num_tiles++; | |
| for (j=i+1; j<f.nb_opid; j++) { | |
| let p = f.opid_props(j, 'CodecID'); | |
| if (p == 'hvt1') { | |
| //check this tile pid sinks intersect the current pid sinks | |
| let t_sinks = f.opid_sinks(j); | |
| const found = t_sinks.some(r => sinks.includes(r)) | |
| if (found) num_tiles++; | |
| } | |
| } | |
| } | |
| if (i) { | |
| str=' '; | |
| let k=0; | |
| while (1) { | |
| text.set_text(str); | |
| let slen = text.measure().width; | |
| if (slen >= len) break; | |
| str += ' '; | |
| } | |
| } | |
| sub_str = str; | |
| if (f.nb_opid>1) { | |
| if (num_tiles>1) { | |
| sub_str += '#tilePIDs[' + num_tiles + ']'; | |
| } else { | |
| sub_str += '#' + f.opid_props(i, 'name'); | |
| } | |
| } | |
| if (!sinks.length) { | |
| str_list.push(sub_str + ': Not connected'); | |
| continue; | |
| } | |
| for (j=0; j<sinks.length; j++) { | |
| let sf = sinks[j]; | |
| if (!j) { | |
| text.set_text(sub_str); | |
| sub_len = text.measure().width; | |
| } else { | |
| sub_str=' '; | |
| let k=0; | |
| while (1) { | |
| text.set_text(sub_str); | |
| let slen = text.measure().width; | |
| if (slen >= sub_len) break; | |
| sub_str += ' '; | |
| } | |
| } | |
| build_graph(sinks[j], sub_str, str_list, fdone); | |
| } | |
| } | |
| fdone.push(f); | |
| } | |
| let stats_translate_y=0; | |
| let stats_graph = []; | |
| function update_stats() | |
| { | |
| let stats = []; | |
| let sys_info='CPU: '+sys.process_cpu_usage + ' Mem: ' + Math.floor(sys.process_memory/1000000) + ' MB'; | |
| if (session.http_bitrate) { | |
| let r = session.http_bitrate; | |
| sys_info += ' HTTP: '; | |
| if (r>1000000) sys_info += '' + Math.floor(r/100000) / 10 + ' mbps'; | |
| else if (r>1000) sys_info += '' + Math.floor(r/100) / 10 + ' kbps'; | |
| else sys_info += '' + r + ' bps'; | |
| } | |
| stats.push(sys_info); | |
| // stats.push(' '); | |
| let i; | |
| for (i=0; i< session.nb_filters; i++) { | |
| let f = session.get_filter(i); | |
| //only output filters (no out, single in) | |
| if ((f.nb_ipid!=1) || f.nb_opid) continue; | |
| let str = f.streamtype; | |
| let src = f.ipid_source(0); | |
| let names = src.name.split(':'); | |
| str += ' (' + names[0]; | |
| let decrate = src.pck_done; | |
| if (!decrate) decrate = src.pck_sent + src.pck_ifce_sent; | |
| decrate /= src.time/1000000; | |
| decrate = Math.floor(decrate); | |
| str += ') ' + decrate + ' f/s'; | |
| let p = f.ipid_props(0, 'Width'); | |
| if (p) { | |
| str += ' ' + p; | |
| p = f.ipid_props(0, 'Height'); | |
| str += 'x' + p; | |
| p = f.ipid_props(0, 'FPS'); | |
| if (p) { | |
| let fps = Math.floor(p.n/p.d); | |
| if (fps * p.d == p.n) | |
| str += '@' + fps; | |
| else | |
| str += '@' + p.n + '/'+p.d; | |
| } | |
| p = f.ipid_props(0, 'SAR'); | |
| if (p) str += ' SAR ' + p.n + '/' + p.d; | |
| p = f.ipid_props(0, 'PixelFormat'); | |
| if (p) str += ' pf ' + p; | |
| } else { | |
| p = f.ipid_props(0, 'SampleRate'); | |
| if (p) { | |
| str += ' ' + p; | |
| p = f.ipid_props(0, 'Channels'); | |
| if (p) str += ' ' + p; | |
| p = f.ipid_props(0, 'ChannelLayout'); | |
| if (p) str += ' ' + p; | |
| p = f.ipid_props(0, 'AudioFormat'); | |
| if (p) str += ' fmt ' + p; | |
| } | |
| } | |
| let buffer = f.ipid_props(0, 'buffer')/1000; | |
| let rebuf = 0; | |
| if (audio_only) { | |
| rebuf = aout.get_arg('rebuffer'); | |
| } | |
| else { | |
| rebuf = vout.get_arg('rebuffer'); | |
| } | |
| if (rebuf>0) { | |
| let play_buf = audio_only ? aout.get_arg('buffer') : vout.get_arg('buffer'); | |
| let pc = Math.floor(100 * buffer / play_buf); | |
| str += ' - rebuffering ' + pc + ' %'; | |
| } else { | |
| str += ' - buffer ' + Math.floor(f.ipid_props(0, 'buffer')/1000) + ' ms'; | |
| } | |
| stats.push(str); | |
| } | |
| // stats.push(' '); | |
| //recompute graph only when initializing the window | |
| if (init_wnd) | |
| stats_graph = []; | |
| if (! stats_graph.length) { | |
| let fdone = []; | |
| //browse all sources, and build graph | |
| for (i=0; i< session.nb_filters; i++) { | |
| let f = session.get_filter(i); | |
| //source filter: no input and outputs | |
| if (f.nb_ipid || !f.nb_opid) continue; | |
| build_graph(f, '', stats_graph, fdone); | |
| } | |
| let not_done = []; | |
| for (i=0; i< session.nb_filters; i++) { | |
| let f = session.get_filter(i); | |
| if (f.alias) continue; | |
| if (fdone.indexOf(f)<0) | |
| not_done.push(f); | |
| } | |
| if (not_done.length) { | |
| stats.push(' '); | |
| let str = "Filters not connected:"; | |
| not_done.forEach(f => { str += ' '+f.name;}); | |
| stats.push(str); | |
| } | |
| } | |
| //push graph to stats string array | |
| stats_graph.forEach(str => { stats.push(str);}); | |
| if (use_libcaca) { | |
| let str = ''; | |
| stats.forEach(a => { | |
| str+=''+a+'\n'; | |
| }); | |
| vout.update('oltxt', str); | |
| return; | |
| } | |
| text.fontsize = 20; | |
| text.set_text(stats); | |
| if (init_wnd) { | |
| let txtdim = text.measure(); | |
| ol_width = Math.floor(txtdim.width*1.2); | |
| while (ol_width%2) ol_width--; | |
| ol_height = Math.floor(txtdim.height*1.4); | |
| while (ol_height%2) ol_height--; | |
| //we resize / realloc the overlay buffer, lock vout and update it | |
| vout.lock(true); | |
| ol_buffer = new ArrayBuffer(ol_width*ol_height*4); | |
| ol_canvas = new evg.Canvas(ol_width, ol_height, 'rgba', ol_buffer); | |
| vout.update('olsize', ''+ol_width+'x'+ol_height); | |
| stats_translate_y = txtdim.height/2; | |
| vout.update('oldata', ol_buffer); | |
| vout.lock(false); | |
| let pos; | |
| if (audio_only) | |
| pos = '0x0x'+ol_width+'x'+ol_height; | |
| else | |
| pos = ''+ Math.floor(ol_width/2 - disp_size.x/2) + 'x' + Math.floor(ol_height/2 - disp_size.y/2) + 'x'+ol_width+'x'+ol_height; | |
| vout.update('olwnd', pos); | |
| init_wnd=false; | |
| } | |
| ol_canvas.clearf(0.2, 0.2, 0.2, 0.6); | |
| let mx = new evg.Matrix2D(); | |
| mx.translate(-ol_width/2, stats_translate_y); | |
| ol_canvas.matrix = mx; | |
| ol_canvas.path = text; | |
| ol_canvas.fill(brush); | |
| //we could lock vout to avoid any tearing ... | |
| vout.update('oldata', ol_buffer); | |
| } | |
| let auth_name=""; | |
| let auth_pass=""; | |
| let auth_state=0; | |
| let auth_modified = false; | |
| let auth_requests = []; | |
| function update_auth() | |
| { | |
| if (init_wnd) { | |
| let pos = '0x0x'+ol_width+'x'+ol_height; | |
| vout.update('olwnd', pos); | |
| init_wnd=false; | |
| progress_bar_path=null; | |
| auth_modified = true; | |
| auth_name = auth_requests[0].user; | |
| auth_pass = auth_requests[0].pass; | |
| } | |
| if (!auth_modified) return false; | |
| auth_modified = false; | |
| ol_canvas.clearf(1, 1, 1, 1); | |
| let str = ['Authentication for ' + auth_requests[0].site]; | |
| str.push(auth_requests[0].secure ? "Secured Connection" : "NOT SECURED Connection"); | |
| if (auth_state) { | |
| str.push('Enter password'); | |
| let p = ''; | |
| let i = auth_pass.length; | |
| while (i) { | |
| p+='*'; | |
| i--; | |
| } | |
| str.push(p); | |
| } else { | |
| str.push('Enter user name'); | |
| str.push(auth_name); | |
| } | |
| let fs = Math.floor(0.1*ol_height); | |
| if (fs > 60) fs=60; | |
| text.fontsize = fs; | |
| text.lineSpacing = 1.5*fs; | |
| text.align = GF_TEXT_ALIGN_CENTER; | |
| text.baseline = GF_TEXT_BASELINE_TOP; | |
| text.set_text(str); | |
| let txtdim = text.measure(); | |
| let mx = new evg.Matrix2D(); | |
| mx.translate(- txtdim.width/2, -0); | |
| ol_canvas.matrix = mx; | |
| ol_canvas.path = text; | |
| brush.set_color('black'); | |
| ol_canvas.fill(brush); | |
| brush.set_color('white'); | |
| //we could lock vout to avoid any tearing ... | |
| vout.update('oldata', ol_buffer); | |
| } | |
| function update_overlay() | |
| { | |
| if (overlay_type==OL_NONE) return; | |
| //help screen | |
| if (overlay_type==OL_HELP) { | |
| update_help(); | |
| return; | |
| } | |
| //play screen | |
| if (overlay_type==OL_PLAY) { | |
| update_play(); | |
| } | |
| //stats screen | |
| if (overlay_type==OL_STATS) { | |
| update_stats(); | |
| } | |
| //auth screen | |
| if (overlay_type==OL_AUTH) { | |
| update_auth(); | |
| } | |
| } | |
| let ol_visible=false; | |
| let oltask_scheduled=false; | |
| function toggle_overlay() | |
| { | |
| ol_visible = !ol_visible; | |
| if (!ol_visible) { | |
| //we don't lock vout because the overlay buffer is still valid | |
| vout.update('oldata', null); | |
| vout.update('oltxt', ''); | |
| overlay_type=OL_NONE; | |
| return; | |
| } | |
| //we will potentially destroy the previous overlay bffer, lock vout | |
| vout.lock(true); | |
| if (!setup_overlay()) { | |
| vout.lock(false); | |
| return; | |
| } | |
| vout.update('olwnd', '0x0x'+ol_width+'x'+ol_height); | |
| vout.update('olsize', ''+ol_width+'x'+ol_height); | |
| vout.update('oldata', ol_buffer); | |
| vout.lock(false); | |
| if (!oltask_scheduled) { | |
| try { | |
| session.post_task( () => { | |
| oltask_scheduled=false; | |
| if (audio_only && aout.ipid_props(0, 'eos') ) { | |
| //we don't lock vout because the overlay buffer is still valid | |
| vout.update('oldata', null); | |
| return false; | |
| } | |
| if (session.last_task) { | |
| return false; | |
| } | |
| update_overlay(); | |
| if (!ol_visible) return false; | |
| if (overlay_type==OL_NONE) return false; | |
| oltask_scheduled=true; | |
| return task_reschedule; | |
| }, "overlay_update"); | |
| oltask_scheduled = true; | |
| } catch (e) {} | |
| } | |
| } | |
| let shortcuts = [ | |
| { "code": "B", "desc": "speeds up by 2"}, | |
| { "code": "V", "desc": "speeds down by 2"}, | |
| { "code": "N", "desc": "normal play speed"}, | |
| { "code": "space", "desc": "pause/resume"}, | |
| { "code": "S", "desc": "step one frame"}, | |
| { "code": "Right", "desc": "seek forward by 1%, 10% if alt down, 30% if ctrl down"}, | |
| { "code": "Left", "desc": "seek backward "}, | |
| { "code": "Up", "desc": "seek forward by 30s, 10m if alt down, 30m if ctrl down"}, | |
| { "code": "Down", "desc": "seek backward"}, | |
| { "code": "I", "desc": "show info and statistics"}, | |
| { "code": "P", "desc": "show playback info"}, | |
| { "code": "F", "desc": "fullscreen mode"}, | |
| { "code": "M", "desc": "flip video"}, | |
| { "code": "R", "desc": "rotate video by 90 degree"}, | |
| ]; | |
| function set_speed(speed) | |
| { | |
| if (audio_only) aout.update('speed', ''+speed); | |
| else { | |
| vout.update('speed', ''+speed); | |
| if (aout) aout.update('speed', ''+speed); | |
| if (speed>2) vout.update('drop', '1'); | |
| else if (speed<=1) vout.update('drop', ''+drop); | |
| } | |
| } | |
| function process_keyboard(evt) | |
| { | |
| if (interactive_scene==1) return false; | |
| if (overlay_type==OL_AUTH) { | |
| return true; | |
| } | |
| if(evt.window != win_id) return true; | |
| switch (sys.keyname(evt.keycode)) { | |
| case 'B': | |
| if (evt.keymods & GF_KEY_MOD_SHIFT) speed *= 1.2; | |
| else speed *= 2; | |
| set_speed(speed); | |
| return true; | |
| case 'V': | |
| if (evt.keymods & GF_KEY_MOD_SHIFT) speed /= 1.2; | |
| else speed /= 2; | |
| set_speed(speed); | |
| return true; | |
| case 'N': | |
| speed = 1; | |
| set_speed(speed); | |
| return true; | |
| case 'space': | |
| paused = !paused; | |
| set_speed(paused ? 0 : speed); | |
| return true; | |
| case 'S': | |
| paused = 2; | |
| vout.update('step', '1'); | |
| if (aout) aout.update('speed', '0'); | |
| check_duration(); | |
| return true; | |
| case 'Right': | |
| if (interactive_scene) return false; | |
| do_seek(1, evt.keymods, false); | |
| return true; | |
| case 'Left': | |
| if (interactive_scene) return false; | |
| do_seek(-1, evt.keymods, false); | |
| return true; | |
| case 'Up': | |
| if (interactive_scene) return false; | |
| do_seek(1, evt.keymods, true); | |
| return true; | |
| case 'Down': | |
| if (interactive_scene) return false; | |
| do_seek(-1, evt.keymods, true); | |
| return true; | |
| case 'F': | |
| if (audio_only) return; | |
| fullscreen = !fullscreen; | |
| vout.update('fullscreen', ''+fullscreen); | |
| return true; | |
| case 'H': | |
| if (overlay_type==OL_AUTH) break; | |
| //hide player | |
| if (audio_only && (overlay_type==OL_PLAY)) { | |
| toggle_overlay(); | |
| } | |
| overlay_type=OL_HELP; | |
| toggle_overlay(); | |
| //show player | |
| if (!ol_visible && audio_only) { | |
| overlay_type=OL_PLAY; | |
| toggle_overlay(); | |
| } | |
| return true; | |
| case 'I': | |
| if (overlay_type==OL_AUTH) break; | |
| //hide player | |
| if (audio_only && (overlay_type==OL_PLAY)) { | |
| toggle_overlay(); | |
| } | |
| overlay_type=OL_STATS; | |
| toggle_overlay(); | |
| //show player | |
| if (!ol_visible && audio_only) { | |
| overlay_type=OL_PLAY; | |
| toggle_overlay(); | |
| } | |
| return true; | |
| case 'P': | |
| if (overlay_type==OL_AUTH) break; | |
| //do not untoggle for audio only | |
| if (audio_only) return; | |
| overlay_type=OL_PLAY; | |
| if (!ol_visible) init_wnd=true; | |
| toggle_overlay(); | |
| return true; | |
| case 'R': | |
| if (audio_only) return; | |
| rot = vout.get_arg('vrot'); | |
| rot++; | |
| if (rot==4) rot = 0; | |
| if (!rot) vout.update('vrot', '0'); | |
| else if (rot==1) vout.update('vrot', '90'); | |
| else if (rot==2) vout.update('vrot', '180'); | |
| else vout.update('vrot', '270'); | |
| return true; | |
| case 'M': | |
| if (audio_only) return; | |
| flip = vout.get_arg('vflip'); | |
| flip++; | |
| if (flip==4) flip = 0; | |
| if (!flip) vout.update('vflip', 'no'); | |
| else if (flip==1) vout.update('vflip', 'v'); | |
| else if (flip==2) vout.update('vflip', 'h'); | |
| else vout.update('vflip', 'vh'); | |
| return; | |
| default: | |
| break; | |
| } | |
| return false; | |
| } | |
| session.set_auth_fun( (site, user, pass, secure, auth_cbk) => { | |
| overlay_type_bck = overlay_type; | |
| overlay_type = OL_AUTH; | |
| auth_cbk.site = site; | |
| auth_cbk.user = user ? user : ""; | |
| auth_cbk.pass = pass ? pass : ""; | |
| auth_cbk.secure = secure; | |
| auth_requests.push(auth_cbk); | |
| if (overlay_type_bck) toggle_overlay(); | |
| toggle_overlay(); | |
| }); | |