blob: d65b4c0d58f668cd61b479c920d709e242fe8ac4 [file] [edit]
<!doctype html>
<html lang="en-us" data-theme="light">
<head>
<meta charset="utf-8">
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<title>Run MP4Box & GPAC in your browser !</title>
<link rel="icon" type="image/x-icon" href="favicon-32x32.png">
<link
rel="stylesheet"
href="https://cdn.jsdelivr.net/npm/@picocss/pico@2/css/pico.red.min.css"
/>
<style>
:root {
font-size: 14px;
}
body {
font-family: arial;
margin: 0;
padding: none;
}
#logo {
display: flex;
justify-content: flex-start;
align-items: center;
}
#menu {
height: 5em;
color: rgb(20, 20, 20);
}
nav#menu li {
padding-top: 0;
padding-bottom: 0;
}
#menu label {
margin: 0;
}
#logo svg:first-child {
margin-right: .5em;
}
svg.icon-theme-toggle.moon g circle {
transform: scale(1.4);
transition-delay: 0s;
}
svg.icon-theme-toggle.moon g path {
transform: scale(.75);
transition-delay: 0s;
}
svg.icon-theme-toggle g circle, svg.icon-theme-toggle g path {
transform-origin: center;
transition: transform calc(var(--theme-toggle-duration)* .65) cubic-bezier(0,0,0,1.25) calc(var(--theme-toggle-duration)* .35);
}
svg.icon-theme-toggle :first-child path {
transition-duration: calc(var(--theme-toggle-duration)* .6);
transition-property: transform, d;
transition-timing-function: cubic-bezier(0,0,.5,1);
}
svg.icon-theme-toggle.moon :first-child path {
d: path("M-9 3h25a1 1 0 0017 13v30H0Z");
transition-delay: calc(var(--theme-toggle-duration)* .4);
transition-timing-function: cubic-bezier(0,0,0,1.25);
}
.console-wrapper {
background-color: rgb(8, 8, 8);
}
.console {
width: 100%;
background-color: black;
color: white;
font-family: 'Lucida Console', Monaco, monospace;
overflow-y: scroll;
padding: 0.5rem !important;
}
.cwd {
color: var(--pico-primary-hover);
height: 8vh;
}
#cmd_div {
font-size: small;
}
#cmd {
height: 9vh;
margin: 0;
}
#run, #cancel {
width: 6em;
}
#fsRunForm{
display: flex;
gap: 10px;
}
#flush{
width: 8em;
}
#output-wrapper {
display: flex;
align-items: top;
gap: 0;
margin: none;
padding: none;
}
#output {
height: 65vh;
resize: vertical;
}
#output:not([readonly]):is(:active,:focus) {
border: var(--pico-border-width) solid var(--pico-border-color);
}
#controls {
height: 10vh;
gap: 1em;
display: flex;
justify-content: center;
align-items: center;
}
/* @FIXME overlay fullscren on canvas */
#fullscreen {
display: none;
}
#status {
font-size: small;
font-weight: bold;
align-items: center;
margin-bottom: 0.5em;
color: var(--pico-primary);
}
</style>
</head>
<body>
<nav id="menu" class="container-fluid">
<ul>
<li>
<a id="logo" href="https://gpac.io/" target="_blank">
<svg viewBox="0 0 2000 2000" width="2.5em" height="2.5em" xml:space="preserve">
<defs>
<style>
.cls-1{fill:#8f182b;}
.cls-2{fill:#ef524f;}
.cls-3{fill:#ba2030;}
.cls-4{fill:#cd2044;}
.cls-5{fill:#d92f32;}
.cls-6{fill:#1a1c4a;}
</style>
</defs>
<g transform-origin="997.7 537.6">
<polygon class="cls-1" points="1291.1 306.35 704.27 306.35 997.68 768.75 1291.1 306.35 1291.1 306.35"/>
<rect class="cls-6" x="542.76" y="761.44" width="906.68" height="906.68" rx="453.34"/>
<animateTransform id="logo_anim" attributeName="transform" attributeType="XML" type="scale" values="1 1; -1 1; 1 1" keyTimes="0; 0.5; 1" dur="1s" repeatCount="indefinite"/>
</g>
<g>
<path class="cls-2" d="M1000,330.1a666.82,666.82,0,1,1-260.67,52.52A665.55,665.55,0,0,1,1000,330.1m0-284.51C472.89,45.59,45.59,472.89,45.59,1000s427.3,954.41,954.41,954.41,954.41-427.3,954.41-954.41S1527.11,45.59,1000,45.59Z"/>
<path class="cls-3" d="M1408.86,1857.84a950.83,950.83,0,0,1-403.56,89.25c-527.12,0-954.42-427.3-954.42-954.41,0-188.39,54.58-364,148.81-512-1.85,5.14-3.66,10.31-5.39,15.49C121.83,627.22,81,775.58,81,932.75c0,531.12,466.61,961.67,1042.19,961.67A1124.55,1124.55,0,0,0,1408.86,1857.84Z"/>
<path class="cls-4" d="M1683.45,1664.24a957,957,0,0,1-274.59,193.6,1124.55,1124.55,0,0,1-285.7,36.58C547.58,1894.42,81,1463.87,81,932.75c0-157.17,40.86-305.53,113.33-436.54-.49,1.45-1,2.9-1.46,4.34,0,0,0,0,0,0s0,0,0,.05A881.86,881.86,0,0,0,118,856.32c0,523.63,460,948.1,1027.47,948.1C1342.73,1804.42,1527,1753.14,1683.45,1664.24Z"/>
<path class="cls-5" d="M1834.94,1464.76a956.45,956.45,0,0,1-151.47,199.46s0,0,0,0c-156.46,88.9-340.72,140.18-537.94,140.18C578.06,1804.42,118,1380,118,856.32A881.86,881.86,0,0,1,192.8,500.64a865.83,865.83,0,0,0-44,272.85c0,514.46,452,931.5,1009.5,931.5C1418.62,1705,1655.92,1614.09,1834.94,1464.76Z"/>
</g>
<g transform="translate(5.6 15)">
<text id="progress" stroke="black" stroke-width="0.1" fill="white" font-size="3.5" font-weight="bold"> </text>
</g>
</svg>
<svg id="gpac-brand" data-name="gpac-brand" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 671.14 188.48" height="1em" >
<defs>
<style>.cls-1{fill:#1a1c4a;}</style>
</defs>
<path class="cls-1" d="M509.26,203h-79.6L415,243.58H389.86l66-187.82h27.49l65.72,187.82H523.93ZM469.46,85.08l-33,98.54h66Z" transform="translate(-64.43 -55.76)"/>
<path class="cls-1" d="M651,56.9c39.54,0,71,19.9,84.58,55.25H707c-9.95-22-29.33-34.3-56-34.3-38.22,0-66.77,27.75-66.77,72.53,0,44.51,28.55,72.27,66.77,72.27,26.71,0,46.09-12.31,56-34h28.54c-13.62,35.08-45,54.72-84.58,54.72-51,0-91.12-38.23-91.12-93S599.94,56.9,651,56.9Z" transform="translate(-64.43 -55.76)"/>
<path class="cls-1" d="M240.13,113.06H211.59c-10-21.21-29.85-33.52-55.51-33.52-38.49,0-67.3,27.76-67.3,71.75,0,43.73,28.81,71.48,67.3,71.48,35.87,0,60.48-23,64.15-59.18h-72.8V144.22h98.2v18.33c-4.19,45.3-40.07,81.69-89.55,81.69-51.32,0-91.65-38.23-91.65-93s40.33-93.22,91.65-93.22C194.57,58.07,226.51,78,240.13,113.06Z" transform="translate(-64.43 -55.76)"/>
<path class="cls-1" d="M336,162.84H300.88v80.74H277.05v-187H336c42.94,0,63.1,23.57,63.1,53.42C399.07,137.7,381.26,162.84,336,162.84Zm0-19.64c27.23,0,38.75-12.83,38.75-33.25,0-21.21-11.52-33.78-38.75-33.78H300.88v67Z" transform="translate(-64.43 -55.76)"/>
<rect class="cls-1" x="145.07" y="88.42" width="97.98" height="18.62"/>
</svg>
</a>
</li>
<li>
<em>experimental wasm playground</em>
</li>
<li>
<label>
<input id="threading" name="threading" type="checkbox" role="switch" />
<em>threaded</em>
</label>
</li>
</ul>
<ul>
<li>
<a class="contrast" aria-label="Switch to dark mode" onclick="
const html = document.getElementsByTagName('html')[0];
const theme = html.getAttribute('data-theme');
const target = theme == 'light' ? 'dark' : 'light';
html.setAttribute('data-theme', target);
this.setAttribute('aria-label', `Switch to ${theme} mode`);
const toggle = document.querySelector('svg.icon-theme-toggle');
if(target == 'light'){
toggle.classList.remove('moon');
} else {
toggle.classList.add('moon');
}
">
<svg id="icon-theme-toggle" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 32 32" fill="currentColor" class="icon-theme-toggle">
<clipPath id="theme-toggle-cutout"><path d="M0-11h25a1 1 0 0017 13v30H0Z"></path></clipPath>
<g clip-path="url(#theme-toggle-cutout)">
<circle cx="16" cy="16" r="8.4"></circle>
<path d="M18.3 3.2c0 1.3-1 2.3-2.3 2.3s-2.3-1-2.3-2.3S14.7.9 16 .9s2.3 1 2.3 2.3zm-4.6 25.6c0-1.3 1-2.3 2.3-2.3s2.3 1 2.3 2.3-1 2.3-2.3 2.3-2.3-1-2.3-2.3zm15.1-10.5c-1.3 0-2.3-1-2.3-2.3s1-2.3 2.3-2.3 2.3 1 2.3 2.3-1 2.3-2.3 2.3zM3.2 13.7c1.3 0 2.3 1 2.3 2.3s-1 2.3-2.3 2.3S.9 17.3.9 16s1-2.3 2.3-2.3zm5.8-7C9 7.9 7.9 9 6.7 9S4.4 8 4.4 6.7s1-2.3 2.3-2.3S9 5.4 9 6.7zm16.3 21c-1.3 0-2.3-1-2.3-2.3s1-2.3 2.3-2.3 2.3 1 2.3 2.3-1 2.3-2.3 2.3zm2.4-21c0 1.3-1 2.3-2.3 2.3S23 7.9 23 6.7s1-2.3 2.3-2.3 2.4 1 2.4 2.3zM6.7 23C8 23 9 24 9 25.3s-1 2.3-2.3 2.3-2.3-1-2.3-2.3 1-2.3 2.3-2.3z"></path>
</g>
</svg>
</a>
</li>
<li>
<a href="#help" target="blank">Console usage</a>
</li>
<li>
<a href="https://wiki.gpac.io/" target="blank">GPAC's wiki</a>
</li>
<li>
<a href="https://github.com/gpac/gpac/labels/wasm" target="blank">Support</a>
</li>
<li>
<a href="https://github.com/gpac/gpac" target="blank">Github</a>
</li>
</ul>
</nav>
<div id="status" class="container-fluid">
Downloading...
</div>
<div id="output-wrapper" class="container-fluid">
<div class="console-wrapper">
<canvas id="canvas" tabindex=-1 width="0" height="0"></canvas>
</div>
<div id="output" class="console"></div>
</div>
<!-- command line input -->
<div id="cmd_div" class="container-fluid">
<fieldset class="console-wrapper">
<textarea id="cmd" class="console" placeholder="Enter a command ..." spellcheck="false" autofocus autocomplete="false"
onkeypress="
if (event.keyCode == 13) {
if (!is_custfs) {
document.getElementById('output').innerHTML += this.value + '<br>';
}
process_command();
event.preventDefault();
}
" onkeyup="
let idx=cmd_idx;
if (event.keyCode == 38) { idx++; } // UP
else if (event.keyCode == 40) { idx--;} // DOWN
else {
if (event.keyCode == 17) ctrl_down=false;
else if (event.keyCode == 18) opt_down=false;
if (document.activeElement === LIBGPAC['canvas']) {
event.preventDefault();
set_status('canvas is def');
}
return;
}
if (idx>=cmd_history.length) return;
var cmd_e = document.getElementById('cmd');
if (idx<0) {
cmd_idx = -1;
cmd_e.value = '';
return;
}
cmd_idx = idx;
cmd_e.value = cmd_history[cmd_history.length-1-idx];
if (event.ctrlKey) {
switch (event.keyCode) {
case 67: // Ctrl+C
setCommand('');
break;
case 76: // Ctrl+L
outputElement.innerHTML = '';
break;
case 65: // Ctrl+A
cmdElement.selectionStart = 0;
cmdElement.selectionEnd = 0;
break;
case 69: // Ctrl+E
cmdElement.selectionStart = cmdElement.value.length;
cmdElement.selectionEnd = cmdElement.value.length;
break;
}
}
" onkeydown="
if (event.keyCode == 17) ctrl_down=true;
else if (event.keyCode == 18) opt_down=true;
if (document.activeElement === LIBGPAC['canvas'])
{
event.preventDefault();
}
"></textarea>
</fieldset>
</div>
<!-- session controls -->
<div id="controls" class="container-fluid">
<select disabled id="commands" aria-label="select an example">
<option selected="" value="-1">Select an example</option>
</select>
<input id="run" type="button" value="Run &#10550;" disabled/>
<input id="cancel" type="button" value="Cancel" hidden/>
<form id="fsRunForm" hidden>
<label>
<input id="reports" type="checkbox" role="switch" onclick="GPAC.toggle_reporting(4);"/>
reporting
</label>
<fieldset role="group">
<select name="flush" aria-label="Select Flush behavior on abort" id="flush">
<option value="1">Full Flush</option>
<option value="2">Fast Flush</option>
<option value="3" selected>No Flush</option>
</select>
<input id="abort" type="button" name="abort" value="Abort" onclick="
let el = document.getElementById('flush');
let val = el.options[el.selectedIndex].value;
GPAC.toggle_reporting(val);
console.log(val);
" />
</fieldset>
</form>
<input type="button" value="Fullscreen" id="fullscreen" onclick="
LIBGPAC['canvas'].requestFullscreen();
">
</div>
<div id="help" class="container-fluid">
<h2>Console usage</h2>
<p>This page allows running the <strong>MP4Box</strong> and <strong>GPAC</strong> applications in web browsers.</p>
<p>The webassembly build is experimental. It tries to uses webcodecs whenever decoding or encoding is involved. If webcodecs can't be used, it falls back to software implementation running in wasm.</p>
<p>Select an example and make edits, or type in your own command. Finally press <code>Enter</code> key or click the <code>Run</code> button.</p>
<p>Remote <strong>DASH</strong> and <strong>HLS</strong> playlists can be used as input, provided that they are hosted with proper <strong>CORS</strong> configurations.</p>
<p>The console also provides basic shell file system, commands to import media export media files, and support advanced gpac configuration, as described below:</p>
<h3>
File import / export
</h3>
<ul>
<li><code>imp [-d] [N]</code>: imports files or directory <code>-d</code> to local filesystem file.</li>
<li><code>imp</code> : imports single file to current dir using its native name</li>
<li><code>imp NAME</code> : imports single file as <code>NAME</code></li>
<li><code>imp NAME/</code> : imports one or more files in <code>NAME</code> using their native name</li>
<li><code>imp -d</code> : imports a directory to current dir</li>
<li><code>imp -d NAME</code> : imports a directory as <code>NAME</code></li>
<li><code>exp NAME</code>: exports given file <code>NAME</code></li>
</ul>
<p><a href="https://developer.mozilla.org/en-US/docs/Web/API/IndexedDB_API">IndexedDB</a> is used for persistent storage, mounted at <code>/idbfs</code>. Typing any command will synchronize the file system.</p>
<h4>Handling large files</h4>
<p>The variable <code>$(NAME)</code> can be used to design a file not on Emscripten's file system.
The file will be handled as a stream (not copied). This is the recommended methods for handling large files.</p>
<p>If <code>NAME</code> is an absolute URL other than <code>file://</code> scheme:
</p><ul>
<li>
if used for an input, it will be pulled progressively using <a href="https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API">Fetch API</a>.
</li>
<li>
if used for an output, it will be pushed progressively using <a href="https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API">Fetch API</a>. If writable streams fail, the file will be stored in memory until final PUT or canceled.<p></p>
</li>
</ul>
<p>Otherwise, <code>NAME</code> designs a file to be picked on the user's file system
</p><ul>
<li>
if used for an input, a file picker will be shown and status text will indicate the corresponding <code>NAME</code>
</li>
<li>
if used for an output, a file saver will be shown with the given <code>NAME</code>. If writable stream failure, the file will be stored in memory until final write or canceled.<p></p>
</li>
</ul>
<p>For output files, the extension will be used to derive the output format, e.g. <code>($file.ts)</code> will use MPEG-2 TS.
Use <code>$(file):ext=ts</code> to override format if desired. <em>This syntax CANNOT work when editing files (MP4Box, dasher context, etc...).</em></p>
<h3>Shell commands</h3>
<ul>
<li><code>↑</code><code>↓</code> : Use up &amp; down arrows can be used for command history.</li>
<li><code>ls [N]</code>: list files in <code>N</code> or in current directory if <code>N</code> not set or print file N info - use 'ls -l N' for details.</li>
<li><code>cd [NAME]</code>: changes current working directory, use Emscripten home if <code>NAME</code> not provided</li>
<li><code>cwd</code>: prints current working directory</li>
<li><code>clear</code>: clears console output</li>
<li><code>mkdir NAME</code>: creates directory <code>NAME</code></li>
<li><code>rm NAME</code>: removes file or empty directory <code>NAME</code></li>
<li><code>mv N1 N2</code>: moves file <code>N1</code> to <code>N2</code></li>
<li><code>cp N1 N2</code>: copies file <code>N1</code> to <code>N2</code> (no recursive copy support)</li>
<li><code>show NAME</code>: shows content of file <code>NAME</code></li>
<li><code>edit NAME</code>: edits content of file <code>NAME</code>, use <code>save</code> to save and close, <code>close</code> to discard and close</li>
</ul>
<h3>Configuration commands</h3>
<ul>
<li class="threaded"><code>worker</code>: toggles worker mode on or off (off)</li>
<li class="threaded"><code>threads</code>: views number of pre-allocated workers</li>
<li><code>stack</code>: prints available size in stack</li>
<li><code>clearh [N]</code>: clears command history. If <code>N</code> is set, only keeps last <code>N</code> items, otherwise clears everything</li>
<li><code>fetch</code>: toggles fetch support in libgpac</li>
<li><code>bsize</code>: sets block size used to read from local disk (default 10000)</li>
<li><code>eval</code>: evaluates JS code</li>
</ul>
<h4>Performances</h4>
<p>The default behaviour for gpac app is to run on the browser/worker main loop in non-blocking mode, running the session at most 100 times before returning control.</p>
<p>This can be configured when calling gpac (see <code>gpac -hx</code>), for example:
</p><ul>
<li>
<code>-step=-1</code> will block the main browser/worker thread (WILL DEADLOCK if some filters use JS promises or other async tools)
</li>
<li>
<code>-step=0</code> will use browser defaults for requestAnimationFrame frequency
</li>
<li>
<code>-step=100</code> will request callback every 10 ms
</li>
<li>
<code>-step=100:1000</code> will request callback every 10 ms, calling at most 1000 times the session<p></p>
</li>
</ul>
<p class="threaded">When running without worker, internal threading of filters (e.g. ffmpeg &amp; co) will be disabled if the session runs only on the main thread.</p>
<p class="threaded">Use <code>-threads=N</code> with <code>N</code> &gt; 0 (typically <code>-threads=1</code>) to enable threading in these filters.</p>
</div>
<script>
const cmd_history = [];
const statusElement = document.getElementById('status');
const progressTxt = document.getElementById('progress').childNodes[0];
const animElement = document.getElementById('logo_anim');
const runElement = document.getElementById('run');
const cancelElement = document.getElementById('cancel');
const flushElement = document.getElementById('flush');
const cmdElement = document.getElementById('cmd');
const reportElement = document.getElementById('reports');
const selCmdElement = document.getElementById('commands');
const outputElement = document.getElementById('output');
const abortElement = document.getElementById('abort');
const threadingElement = document.getElementById('threading');
let console_bck = '';
let cmd_idx = -1;
let ctrl_down=false;
let opt_down=false;
let use_idbfs = false;
let edit_name = null;
let is_eval = false;
let is_bsize = false;
let is_custfs = false;
let fsess = null;
let prev_filter = null;
let defer_mode = false;
let logs_val = null;
let suspend_sess = false;
let fs_steps = 20;
let FS = null;
let STATIC_SRC;
if (window.location.origin.indexOf('.gpac.io')>0) STATIC_SRC = `${window.location.origin}/data`;
else if (window.location.origin.startsWith('http://')) STATIC_SRC = 'http://wasm.gpac.io/data';
else STATIC_SRC = 'https://wasm.gpac.io/data';
const examples = [
{
"name": "media playback",
"cmd": `gpac -graph -i ${STATIC_SRC}/video_180.mp4 -i ${STATIC_SRC}/audio.mp4 aout vout`,
"info": '<a href="https://wiki.gpac.io/Player/filters-playback/">simple playback</a> of audio and video files. The <code>-graph</code> option prints out the internal graph showing all filters involved in the media processing pipeline.'
},
{
"name": "describe streams available in a media",
"cmd": `gpac -i ${STATIC_SRC}/video_180.mp4 inspect:full`,
"info": 'inspect a media and print its <a href="https://wiki.gpac.io/Filters/Rearchitecture/?h=pid#pid-description">PIDs</a> information, eg. information about the streams it contains.'
},
{
"name": "extract the first 2 seconds of a video",
"cmd": `gpac -i ${STATIC_SRC}/video_180.mp4 reframer:xs=0:xe=T00:00:02 -o xs0-xe02.mp4`,
"info": 'extract the first 2 seconds of a video.'
},
{
"name": "trick play: mux all IDR frames to a single MP4",
"cmd": `gpac -logs=all@info -i ${STATIC_SRC}/video_180.mp4 @#Video reframer:saps=1 @ -o trick_play.mp4`,
"info": "creates a video containing only the IDR frames (SAP=1) from the input source."
},
{
"name": "download remote DASH content",
"cmd": `gpac -stats -i ${STATIC_SRC}/dash/bbb_30fps.mpd dashin:forward=file -o dump/$File$:dynext`,
"info": null
}
];
examples.forEach((el, i) => {
if (el.cmd){
let opt = document.createElement("option");
opt.text = el.name;
opt.value = i;
selCmdElement.add(opt);
}
});
selCmdElement.onchange = () => {
let idx = parseInt(selCmdElement.value);
if (idx<0) return;
let item = examples[idx];
setCommand(item.cmd.trim());
};
function setCommand(value) {
cmdElement.value = value == '' ? null : value;
};
function onGpacReady() {
cmdElement.removeAttribute("disabled");
selCmdElement.removeAttribute("disabled");
runElement.removeAttribute("disabled");
cmdElement.setAttribute("placeholder", "Enter a command ...");
outputElement.setAttribute("aria-busy", "false");
outputElement.innerHTML += '<span class="version">GPAC' + LIBGPAC.version() + '</span><br><br>' + consolePrefix();
};
function toggle_eval_mode() {
is_eval = !is_eval;
let cmdDiv = document.getElementById('cmd_div');
if (is_eval){
outputElement.readonly = false;
outputElement.setAttribute('contenteditable', true);
outputElement.innerText = '';
set_status('Edit your JS and click run to evaluate, cancel to abort');
runElement.hidden = false;
cancelElement.hidden = false;
cmdDiv.hidden = true;
selCmdElement.hidden = true;
fsRunForm.hidden = true;
reportElement.hidden = true;
} else {
outputElement.setAttribute('contenteditable', false);
outputElement.readonly = true;
runElement.hidden = false;
cancelElement.hidden = true;
cmdDiv.hidden = false;
selCmdElement.hidden = false;
fsRunForm.hidden = true;
reportElement.hidden = false;
}
displayStatus('');
}
function run_session() {
let i=fs_steps;
while (i) {
let e = LIBGPAC.fs_run(fsess);
if (e) break;
i--;
}
if (suspend_sess || LIBGPAC.fs_last_task(fsess)) {
animElement.stop();
if (!suspend_sess) set_status('Session done');
suspend_sess=false;
} else {
setTimeout(run_session, 0);
}
}
//start anim on logo
animElement.start = function() {
if (! this.started) {
this.setAttribute('dur', '2s');
this.beginElement();
this.started=true;
}
};
//stop anim on logo
animElement.stop = function() {
this.endElementAt(0);
this.setAttribute('dur', 'indefinite');
this.started=false;
};
runElement.onclick = function() {
if (is_eval){
run_script();
} else {
if (!GPAC.running){
if ( cmdElement.value.trim() == '' ) return;
process_command();
setCommand('');
return;
}
if(GPAC.running){
GPAC.toggle_reporting(flushElement.value);
}
}
}
cancelElement.onclick = toggle_eval_mode
//create missing dirs in an output path
function check_file_dir(fname, is_directory) {
let paths = fname.split('/');
let path ='/';
while (paths.length) {
if (!paths[0].length) {
paths.shift();
continue;
}
path += paths[0];
if ((paths.length==1) && !is_directory) break;
if (paths.length>1) path+='/';
try {
FS.mkdir(path);
} catch(e) {
}
paths.shift();
}
}
//set status
function set_status(str)
{
statusElement.innerHTML = str;
}
function do_log(str) {
LIBGPAC.print(str);
}
function do_log_err(str) {
LIBGPAC.printErr(str);
}
//load command history from /idbfs/.gpac/history.txt
function load_history() {
let s = FS.analyzePath('/idbfs', true);
if (s.error) {
set_status('IDBFS not mounted');
return;
}
use_idbfs = true;
s = FS.analyzePath('/idbfs/.gpac', true);
if (s.error) FS.mkdir('/idbfs/.gpac');
s = FS.analyzePath('/idbfs/.gpac/history.txt', true);
if (s.error) return;
let strs = FS.readFile('/idbfs/.gpac/history.txt', { encoding: "utf8"}).split('\n');
strs.forEach(s => { if (s.length && (cmd_history.indexOf(s)<0)) cmd_history.push(s); });
}
//save command history to /idbfs/.gpac/history.txt
function save_history() {
if (!use_idbfs) {
return;
}
let txt='';
cmd_history.forEach( c => { txt += c + '\n';});
FS.writeFile('/idbfs/.gpac/history.txt', txt);
FS.syncfs(false, () => {
if (arguments.length) {
err('Failed to sync IDBFS: ' + arguments[0]);
}
});
}
//runs a given script
function run_script(cmd) {
toggle_eval_mode();
var js_txt = outputElement.innerText;
outputElement.removeAttribute('contenteditable');
outputElement.innerText = '';
set_status('');
err = '';
console.log('text is ' + js_txt);
if (!cmd.length || (cmd=='go')) {
var fun = Function(js_txt);
try {
fun();
} catch (e) {
err = e.message;
}
}
return com_exit(err);
}
function set_text(txt)
{
if (txt.length) outputElement.innerText += txt;
else outputElement.innerText = '';
}
//utility to log command error and append '/cwd/ %' at end of textArea
function com_exit() {
if (arguments.length && arguments[0].length) {
do_log('Error: ' + arguments[0]);
}
GPAC.no_log = true;
outputElement.innerHTML += `<br>${consolePrefix()}`;
selCmdElement.hidden = false;
fsRunForm.hidden = true;
runElement.hidden = false;
setCommand('');
}
function onCommandRun(cmd) {
// reset select example
selCmdElement.value = -1;
selCmdElement.hidden = true;
runElement.hidden = true;
fsRunForm.removeAttribute("hidden");
set_status('');
};
function expandFilePath(fp) {
return fp.startsWith('/') ? fp : `${FS.cwd()}/${fp}`
}
//our basic terminal emulation
function process_command() {
const cmd = cmdElement.value;
if (cmd.trim() == ''){
return;
}
onCommandRun(cmd);
GPAC.no_log = false;
args = cmd.trim().split(' ');
//evaluates a script - turns textArea in write mode and wait for go/cancel
if (args[0]=='eval') {
toggle_eval_mode();
return;
}
//in eval mode, run
if (is_eval) {
run_script(cmd);
return;
}
if (is_custfs) {
run_prompt(cmd);
setCommand('');
return;
}
if (is_bsize) {
let s = parseInt(cmd);
is_bsize = false;
set_status('');
if (s != s) return com_exit('Inavlid value ' + cmd);
GPAC.read_block_size = s;
return com_exit();
}
//nothing to do
if (!cmd.length) {
return com_exit();
}
//in edit mode, save/discard content
if (edit_name != null) {
let do_save = false;
if (args[0]=='save') {
do_save = true;
} else if (args[0] != 'close') {
set_status('Use save or close to exit editor, not ' + args[0]);
return;
}
outputElement.removeAttribute('contenteditable');
if (do_save) {
FS.writeFile(edit_name, outputElement.innerText);
}
outputElement.innerHTML = '';
edit_name = null;
return com_exit();
}
//save history
let cmd_idx = cmd_history.indexOf(cmd);
if (cmd_idx>=0) {
cmd_history.splice(cmd_idx, 1);
}
cmd_history.push(cmd);
save_history();
cmd_idx = -1;
//file or directory import
if (args[0]=='imp') {
let is_mdir = false; // import directory content
let file_name = null;
let is_dir = false; // import into directory ?
args.shift();
if (args.length && (args[0]=='-d')) {
is_mdir = true;
args.shift();
}
if (args.length){
file_name = args[0];
is_dir = (file_name.charAt(file_name.length-1) == '/');
if (is_mdir && !is_dir) {
file_name += '/';
}
file_name = expandFilePath(file_name);
check_file_dir(file_name, is_dir);
}
if (is_mdir)
is_dir=true;
let input = document.createElement('input');
input['type'] = 'file';
if (!is_mdir) {
input.multiple = is_dir;
} else {
input.directory = true;
input.mozDirectory = true;
input.webkitdirectory = true;
}
input.click();
input.onchange = function() {
for (const file of event.target.files) {
if (file.name.charAt(0) == '.') continue;
let read = new FileReader();
let fname = file_name; //can be null
if (is_dir) {
if (is_mdir) {
if (fname) {
//strip dir name
let name = file.webkitRelativePath.substring(file.webkitRelativePath.indexOf("/") + 1);
fname += name;
} else {
//copy as is
fname = file.webkitRelativePath;
}
} else {
fname += file.name;
}
}
if (!fname) fname = file.name;
read.addEventListener('loadend', (e) => {
const uint8_view = new Uint8Array(read.result);
fname = expandFilePath(fname);
if (is_mdir) check_file_dir(fname, false);
FS.writeFile(fname, uint8_view);
});
read.readAsArrayBuffer(file);
}
}
return com_exit();
}
//file or directory listing
if (args[0]=='ls') {
let list=0;
if ((args.length>1) && (args[1]=='-l')) {
list=1;
args.splice(1, 1);
}
else if ((args.length>1) && (args[1]=='-la')) {
list=2;
args.splice(1, 1);
}
var d = ((args.length>1) && args[1].length) ? args[1] : FS.cwd();
try {
let stats = FS.stat(d);
if (FS.isDir(stats.mode)) {
var children = FS.readdir(d, { parent: false });
//do_log('Contents of ' + d + ':');
children.forEach(i => {
let text;
if ((i=='.') || (i=='..')) return;
text=i;
if (list) {
stats = FS.stat(d+'/'+i);
if (list==1) {
if (FS.isDir(stats.mode)) text += ' directory';
else text += ' size ' + stats.size;
text += ' modified ' + stats.mtime.toUTCString();
} else {
text = JSON.stringify(stats);
}
}
do_log(text);
});
} else {
let text;
if (list==2) text = JSON.stringify(stats);
else {
text = '' + stats.size + ' modified ' + stats.mtime;
}
do_log(text);
}
return com_exit();
} catch (e) {
return com_exit('No such file');
}
}
//get current working directory
if (args[0]=='cwd') {
do_log(FS.cwd());
return com_exit();
}
//change directory
if (args[0]=='cd') {
var d = (args.length>1) ? args[1] : '/home/web_user';
FS.chdir(d);
return com_exit();
}
//creates directory
if (args[0]=='mkdir') {
if (args.length>1)
FS.mkdir(args[1]);
return com_exit();
}
//remove file
if (args[0]=='rm') {
if (args.length<2) return com_exit('Missing name');
let stats = FS.stat(args[1]);
if (FS.isDir(stats.mode)) FS.rmdir(args[1]);
else FS.unlink(args[1]);
return com_exit();
}
//rename file
if (args[0]=='mv') {
if (args.length<3) return com_exit('Missing file names');
let stats = FS.stat(args[1]);
if (FS.isDir(stats.mode)) return com_exit(args[1] + ' is a directory');
FS.rename(args[1], args[2]);
return com_exit();
}
//copy file
if (args[0]=='cp') {
if (args.length<3) return com_exit('Missing file names');
let stats = FS.stat(args[1]);
if (FS.isDir(stats.mode)) return com_exit(args[1] + ' is a directory');
FS.writeFile(args[2], FS.readFile(args[1], {encoding: "binary"}));
return com_exit();
}
//export file to user file system
if (args[0]=='exp') {
if (args.length<2) return com_exit('Missing file name');
let stats = FS.stat(args[1]);
if (FS.isDir(stats.mode)) return com_exit(args[1] + ' is a directory');
var blob = new Blob([FS.readFile(args[1], {encoding: "binary"})], {type: "application/octet-stream"});
var a = document.createElement("a");
var url = window.URL.createObjectURL(blob);
a.href = url;
a.download = args[1];
a.click();
window.URL.revokeObjectURL(url);
return com_exit();
}
//show file
if (args[0]=='show') {
if (args.length<2) return com_exit('Missing file name');
let stats = FS.stat(args[1]);
if (FS.isDir(stats.mode)) return com_exit(args[1] + ' is a directory');
let str = FS.readFile(args[1], { encoding: "utf8"});
do_log(str);
return com_exit();
}
//edit file
if (args[0]=='edit') {
if (args.length<2) return com_exit('Missing file name');;
let stats = FS.stat(args[1]);
if (FS.isDir(stats.mode)) return com_exit(args[1] + ' is a directory');
let str = FS.readFile(args[1], { encoding: "utf8"});
edit_name = args[1];
if (outputElement) {
outputElement.setAttribute('contenteditable', true);
outputElement.innerText = str;
}
return;
}
//worker tag set
if (args[0]=='worker') {
if (!LIBGPAC.has_threads) {
LIBGPAC.gpac_worker=false;
return com_exit('GPAC compiled without threads');
}
LIBGPAC.gpac_worker = !LIBGPAC.gpac_worker;
do_log('Worker Thread for main: ' + (LIBGPAC.gpac_worker ? 'on' : 'off') );
return com_exit();
}
//print stack free size
if (args[0]=='stack') {
const get_stack_free = LIBGPAC.cwrap('emscripten_stack_get_free', 'number', [])
do_log('Free stack: ' + get_stack_free() + ' bytes');
return com_exit();
}
//clear console
if (args[0]=='clear') {
if (outputElement) outputElement.innerText = '';
return com_exit();
}
//clear history
if (args[0]=='clearh') {
if (args.length>1)
cmd_history = cmd_history.slice(- parseInt(args[1]) );
else
cmd_history=[];
save_history();
return com_exit();
}
//show allocated threads - usefull to check how many threads were indeed created by the previous run
if (args[0]=='threads') {
if (LIBGPAC.has_threads) {
do_log('Threads Pool Size: ' + LIBGPAC['PThread'].unusedWorkers.length);
return com_exit();
}
return com_exit('GPAC compiled without threads');
}
//worker tag set
if (args[0]=='fetch') {
LIBGPAC.gpac_fetch = !LIBGPAC.gpac_fetch;
do_log('libgpac fetch() ' + (LIBGPAC.gpac_fetch ? 'on' : 'off') );
return com_exit();
}
if (args[0]=='fs') {
set_status('Starting interactive graph builder');
set_text('');
setCommand('');
defer_mode=true;
fs_steps=20;
is_custfs = true;
return;
}
if (args[0]=='bsize') {
set_status('Enter new block size to use (current ' + GPAC.read_block_size + ')');
is_bsize = true;
return;
}
//set logs at runtime (doesn't work when video output is used, text input is always directed to SDL in this case...)
if (args[0]=='logs') {
if (!GPAC.running) {
do_log('GPAC not running');
return;
}
if (args.length>1) {
LIBGPAC.log_set_tools_levels(args[1], 1);
}
return;
}
//
//end of commands
//
//run gpac or mp4box
if (GPAC.running) {
set_status('Already running, ignoring command');
return;
}
let will_block=false;
let is_mp4box=false;
if (args[0].toLowerCase()=='gpac') {
args.shift();
}
else if (args[0].toLowerCase()=='mp4box') {
args.shift();
is_mp4box = true;
if (LIBGPAC.gpac_worker==false) will_block=true;
}
//when using vout or compositor in standalone mode, don't use a worker thread since we run through emscripten_main_loop
let use_video=false;
let has_report = false;
for (let i=0; i<args.length; i++) {
a = args[i];
if (a.includes('vout') || a.includes('-play') || a.includes('-gui')) {
use_video=true;
} else if (a.includes('compositor')) {
if (a.includes('base') || a.includes('gui')) use_video=true;
} else if ((LIBGPAC.gpac_worker==false) && (a.startsWith('-step='))) {
var val = parseInt(a.substring(6));
if (val<0) will_block = true;
}
else if (a.startsWith('-r')) {
has_report = true;
}
//input file io
else if (a=='-ib') {
args[i] = '-i';
let f = args[i+1];
let fio = GFIO.create(f, 1);
let wrap_url = fio.gfio_url();
args[i+1] = wrap_url;
i++;
}
//output file io
else if (a=='-ob') {
args[i] = '-o';
let f = args[i+1];
let fio = GFIO.create(f, 2);
let wrap_url = fio.gfio_url();
args[i+1] = wrap_url;
i++;
}
//look for automated wrappers $(NAME), create fileIOs and replace names
else {
let select_start = a.indexOf('$(');
let select_end = (select_start>=0) ? a.indexOf(')') : 0;
if (select_end) {
let hint = a.substring(select_start, select_end+1);
let fio = GFIO.create(hint, 0); //can only use this for read mode at the time being
let wrap_url = fio.gfio_url();
args[i] = a.replace(hint, wrap_url);
}
}
}
function run_prompt(cmd) {
let args = cmd.split(' ');
if (!args.length) return;
if (args[0]=='q') {
is_custfs=false;
if (fsess) {
LIBGPAC.fs_del(fsess);
fsess=null;
LIBGPAC.uninit();
}
set_status("leaving interactive mode");
set_text('');
setCommand('');
return com_exit();
}
if (args[0]=='d') {
if (!fsess) {
defer_mode = !defer_mode;
set_status("will use defer linking mode: " + (defer_mode ? "yes" : "no"));
}
return;
}
if (args[0]=='l') {
logs_val = args[1];
return;
}
if (args[0]=='s') {
if (args.length>1) {
fs_steps = parseInt(args[1]);
if (fs_steps==0) fs_steps=1;
if (fs_steps<0)
set_status("will use blocking mode");
else
set_status("will use " + fs_steps + " steps per callback");
}
return;
}
if (args[0]=='a') {
if (args.length<2) return set_status('Invalid add command');
if (!fsess) {
let flag = 0;
if (fs_steps>0) flag |= 1<<2;
if (defer_mode) flag |= (1<<13);
LIBGPAC.init(0, null);
if (logs_val)
LIBGPAC.log_set_tools_levels(logs_val, 1);
fsess = LIBGPAC.fs_new_defaults(flag);
set_status("Creating filter session in " + ((fs_steps<0) ? "" : "non-") + "blocking mode" + (defer_mode ? ' - deferred linking' : '') );
if (fs_steps<0) fs_steps=1; //for run_session
}
if (prev_filter && defer_mode) {
LIBGPAC.filter_reconnect_output(prev_filter, null);
prev_filter=null;
}
let fdesc = args[1];
let select_start = fdesc.indexOf('$(');
let select_end = (select_start>=0) ? fdesc.indexOf(')') : 0;
if (select_end) {
let hint = fdesc.substring(select_start, select_end+1);
let fio = GFIO.create(hint, 0); //can only use this for read mode at the time being
let wrap_url = fio.gfio_url();
fdesc = fdesc.replace(hint, wrap_url);
}
const e = LIBGPAC._malloc(LIBGPAC.HEAP32.BYTES_PER_ELEMENT);
LIBGPAC.HEAP32.set([0], e/LIBGPAC.HEAP32.BYTES_PER_ELEMENT);
let f;
if (fdesc.startsWith('src=')) {
fdesc = fdesc.substring(4);
f = LIBGPAC.fs_load_source(fsess, fdesc, null, null, e);
} else if (fdesc.startsWith('dst=')) {
fdesc = fdesc.substring(4);
f = LIBGPAC.fs_load_destination(fsess, fdesc, null, null, e);
} else {
f = LIBGPAC.fs_load_filter(fsess, fdesc, e);
}
prev_filter = f;
let res = LIBGPAC.getValue(e, 'i32');
LIBGPAC._free(e);
set_status("added filter "+args[1]+ " returned: " + LIBGPAC.e2s(res));
return;
}
if (args[0]=='r') {
if (!fsess) return set_status('No valid session, add a filter first');
animElement.start();
run_session();
return;
}
if (args[0]=='c') {
if (fsess) {
suspend_sess = true;
set_status("Suspending session");
}
return;
}
if (args[0]=='clear') {
if (outputElement) outputElement.innerText = '';
return;
}
if (args[0]=='p') {
let idx=0;
let n;
if (!fsess) return set_status('No valid session, add a filter first');
if (!prev_filter) return set_status('No filter loaded, cannot probe');
if (args.length<2) return set_status('No name provided');
if (args.length>2) {
idx=parseInt(args[1]);
n=args[2];
} else {
n=args[1];
}
var p_str = LIBGPAC._malloc(LIBGPAC.HEAP32.BYTES_PER_ELEMENT);
var res = LIBGPAC.filter_probe_link(prev_filter, idx, n, p_str);
if (!res) {
var c_str = LIBGPAC.getValue(p_str, "i32");
var chain = LIBGPAC.UTF8ToString(c_str);
LIBGPAC.free(c_str);
if (chain.length)
set_text('resolved chain from ' + LIBGPAC.filter_get_name(prev_filter) + ' to ' + n + ' is: ' + chain);
else
set_text('Direct connection from from ' + LIBGPAC.filter_get_name(prev_filter) + ' to ' + n + ' is possible');
}
LIBGPAC._free(p_str);
return set_status(res ? ('Failed: '+ LIBGPAC.e2s(res) ) : 'OK');
}
if (args[0]=='l') {
let idx=-1;
let n;
if (!fsess) return set_status('No valid session, add a filter first');
if (!prev_filter) return set_status('No filter loaded, cannot check destinations');
if (args.length>1) {
idx=parseInt(args[1]);
}
var p_str = LIBGPAC._malloc(LIBGPAC.HEAP32.BYTES_PER_ELEMENT);
var res = LIBGPAC.filter_get_destinations(prev_filter, idx, p_str);
if (!res) {
var c_str = LIBGPAC.getValue(p_str, "i32");
var chain = LIBGPAC.UTF8ToString(c_str);
LIBGPAC.free(c_str);
set_text('possible destinations for ' + LIBGPAC.filter_get_name(prev_filter) +': ' + chain);
}
LIBGPAC._free(p_str);
return set_status(res ? ('Failed'+res) : 'OK');
}
if (args[0]=='v') {
if (!fsess) return set_status('No valid session, add a filter first');
LIBGPAC.fs_print_stats(fsess);
LIBGPAC.fs_print_connections(fsess);
return;
}
if (args[0]=='i') {
if (!prev_filter) return set_status('No current fiter, add a filter first');
txt='Filter ' + LIBGPAC.filter_get_name(prev_filter) + ' PID info:\n';
let c_i = LIBGPAC.filter_get_ipid_count(prev_filter);
if (c_i) {
for (let idx=0; idx<c_i; idx++) {
let pid = LIBGPAC.filter_get_ipid(prev_filter, idx);
txt+='Input PID ' + (idx+1) + ' Properties:\n';
let props = LIBGPAC.filter_pid_get_props(pid);
txt+=JSON.stringify(props);
txt+='\n\n';
}
}
let c_o = LIBGPAC.filter_get_opid_count(prev_filter);
if (c_o) {
for (let idx=0; idx<c_o; idx++) {
let pid = LIBGPAC.filter_get_opid(prev_filter, idx);
txt+='Output PID ' + (idx+1) + ' Properties:\n';
let props = LIBGPAC.filter_pid_get_props(pid);
txt+=JSON.stringify(props);
txt+='\n\n';
}
}
if (!c_i && !c_o) txt+='No PIDs - run session';
set_text(txt);
return;
}
set_status("Unknown command "+cmd);
let help="Possible commands:\n";
help+= "- d: set defer linking mode (default: "+ defer_mode + ")\n";
help+= "- s: set number of steps before returning control to JS, use -1 for blocking mode (default: "+ fs_steps + ")\n";
help+= "- l LT: set log tools and levels, same syntax as -logs\n";
help+= "- a DESC: add filter, use src=URL for source, dst=URL for sinks. $(name) replacement rules are allowed\n";
help+= "- r: run or resume session\n";
help+= "- c: suspend session while running\n";
help+= "- v: view connections and stats\n";
help+= "- q: exit custom mode\n";
help+= "\nThe following only apply to the last added filter:\n";
help+= "- i: print pid properties\n";
help+= "- p [pid_idx=0] DESC: probe resolution from pid with index pid_idx of last added filter to filter description DESC\n";
help+= "- l [pid_idx=-1]: list possible destinations for pid of index pid_idx (-1 for all pids) of last added filter\n";
help+= "\n";
set_text(help);
}
let controlElement = document.getElementById('controls');
// controlElement.hidden = is_mp4box ? true : false;
// controlElement.style.display = is_mp4box ? 'none' : 'inline-block';
flushElement.selectedIndex = 0;
reportElement.checked = has_report;
let fsElement = document.getElementById('fullscreen');
fsElement.hidden = use_video ? false : true;
GPAC.running = true;
set_status('Running ' + (is_mp4box ? "MP4Box " : "gpac ") + cmd);
animElement.start();
if (use_video && LIBGPAC.gpac_worker) {
LIBGPAC.gpac_worker = false;
GPAC.restore_worker = true;
//cannot run SDL + main_loop in thread unless for offscreen canvas
do_log('Main video output used, disabling worker mode for this run');
}
//our own version of callMain to deal with:
//- stack save/restore missing in emscripten callMain
//- even if it where there, we may exit aynchronously and can only restore stack on main thread in gpac.on_done
function call_gpac() {
GPAC.stack = LIBGPAC.stackSave();
args.unshift(is_mp4box ? "MP4Box" : "gpac");
var argc = args.length;
var argv = LIBGPAC.stringsToPtr(args);
try {
LIBGPAC["_main"](argc, argv);
} catch (e) {
//unwind thrown by emscripten main
if (e != 'unwind') {
GPAC.on_done();
return com_exit('Exception while running - ' + e);
}
}
};
if (will_block) {
let warn='Worker for main disabled - web page will be blocked!';
do_log(warn);
set_status(warn);
//use setTimeout to have time to display above warning...
setTimeout(call_gpac, 1);
} else {
call_gpac();
}
}
//end of command line processing
// Include ANSI escape codes convert
// @link: https://www.npmjs.com/package/ansi-html
// Reference to https://github.com/sindresorhus/ansi-regex
var _regANSI = /(?:(?:\u001b\[)|\u009b)(?:(?:[0-9]{1,3})?(?:(?:;[0-9]{0,3})*)?[A-M|f-m])|\u001b[A-M]/
var _defColors = {
reset: ['fff', '000'], // [FOREGROUD_COLOR, BACKGROUND_COLOR]
black: '000',
red: 'ff0000',
green: '209805',
yellow: 'e8bf03',
blue: '0000ff',
magenta: 'ff00ff',
cyan: '00ffee',
lightgrey: 'f0f0f0',
darkgrey: '888'
}
var _styles = {
30: 'black',
31: 'red',
32: 'green',
33: 'yellow',
34: 'blue',
35: 'magenta',
36: 'cyan',
37: 'lightgrey'
}
var _openTags = {
'1': 'font-weight:bold', // bold
'2': 'opacity:0.5', // dim
'3': '<i>', // italic
'4': '<u>', // underscore
'8': 'display:none', // hidden
'9': '<del>' // delete
}
var _closeTags = {
'23': '</i>', // reset italic
'24': '</u>', // reset underscore
'29': '</del>' // reset delete
}
;[0, 21, 22, 27, 28, 39, 49].forEach(function (n) {
_closeTags[n] = '</span>'
})
/**
* Converts text with ANSI color codes to HTML markup.
* @param {String} text
* @returns {*}
*/
function ansiHTML (text) {
// Returns the text if the string has no ANSI escape code.
if (!_regANSI.test(text)) {
return text
}
// Cache opened sequence.
var ansiCodes = []
// Replace with markup.
var ret = text.replace(/\033\[(\d+)m/g, function (match, seq) {
if (seq=='0') {
r = '';
ansiCodes.forEach( c => {
if (c == '3') r+='</i>';
else if (c == '4') r+='</b>';
});
ansiCodes.length=0;
return r+'</span>';
}
var ot = _openTags[seq]
if (ot) {
// If current sequence has been opened, close it.
if (!!~ansiCodes.indexOf(seq)) { // eslint-disable-line no-extra-boolean-cast
ansiCodes.pop()
return '</span>'
}
// Open tag.
ansiCodes.push(seq)
return ot[0] === '<' ? ot : '<span style="' + ot + ';">'
}
var ct = _closeTags[seq]
if (ct) {
// Pop sequence
ansiCodes.pop()
return ct
}
return ''
})
// Make sure tags are closed.
var l = ansiCodes.length
;(l > 0) && (ret += Array(l + 1).join('</span>'))
return ret
}
/**
* Customize colors.
* @param {Object} colors reference to _defColors
*/
ansiHTML.setColors = function (colors) {
if (typeof colors !== 'object') {
throw new Error('`colors` parameter must be an Object.')
}
var _finalColors = {}
for (var key in _defColors) {
var hex = colors.hasOwnProperty(key) ? colors[key] : null
if (!hex) {
_finalColors[key] = _defColors[key]
continue
}
if ('reset' === key) {
if (typeof hex === 'string') {
hex = [hex]
}
if (!Array.isArray(hex) || hex.length === 0 || hex.some(function (h) {
return typeof h !== 'string'
})) {
throw new Error('The value of `' + key + '` property must be an Array and each item could only be a hex string, e.g.: FF0000')
}
var defHexColor = _defColors[key]
if (!hex[0]) {
hex[0] = defHexColor[0]
}
if (hex.length === 1 || !hex[1]) {
hex = [hex[0]]
hex.push(defHexColor[1])
}
hex = hex.slice(0, 2)
} else if (typeof hex !== 'string') {
throw new Error('The value of `' + key + '` property must be a hex string, e.g.: FF0000')
}
_finalColors[key] = hex
}
_setTags(_finalColors)
}
/**
* Reset colors.
*/
ansiHTML.reset = function () {
_setTags(_defColors)
}
/**
* Expose tags, including open and close.
* @type {Object}
*/
ansiHTML.tags = {}
if (Object.defineProperty) {
Object.defineProperty(ansiHTML.tags, 'open', {
get: function () { return _openTags }
})
Object.defineProperty(ansiHTML.tags, 'close', {
get: function () { return _closeTags }
})
} else {
ansiHTML.tags.open = _openTags
ansiHTML.tags.close = _closeTags
}
function _setTags (colors) {
// reset all
_openTags['0'] = 'font-weight:normal;opacity:1;color:#' + colors.reset[0] + ';background:#' + colors.reset[1]
// inverse
_openTags['7'] = 'color:#' + colors.reset[1] + ';background:#' + colors.reset[0]
// dark grey
_openTags['90'] = 'color:#' + colors.darkgrey
for (var code in _styles) {
var color = _styles[code]
var oriColor = colors[color] || '000'
_openTags[code] = 'color:#' + oriColor
code = parseInt(code)
_openTags[(code + 10).toString()] = 'background:#' + oriColor
}
}
ansiHTML.reset()
//
//Until end of this script: GFIO wrapper
//
//This demo comes with a GF_FileIO wrapper for
// - Emscripten FS file access
// - User file system access
// - protocols supported by Fetch() API
//
// It is not optimized but can help you design your own fileIOs for custom GPAC@WASM integration
const consolePrefix = () => {
return "<span class=\"cwd\">"+FS.cwd()+"</span>$ ";
};
//check support for fetch upload using readable stream
const supportsRequestStreams = (() => {
let duplexAccessed = false;
const hasContentType = new Request('', {
body: new ReadableStream(),
method: 'POST',
get duplex() {
duplexAccessed = true;
return 'half';
},
}).headers.has('Content-Type');
return duplexAccessed && !hasContentType;
})();
//our GFIO object
var GFIO = {
//all defined GF_IOs
dict: [],
//constants used by gf_fileio_set_write_state
WRITE_READY: 0,
WRITE_WAIT: 1,
WRITE_CANCEL: 2,
//default method used for upload
UPLOAD_METHOD: 'POST',
//allow defer upload of file (aggregates to mem then basic POST/PUT or ar->blob->download)
ALLOW_WRITE_DEFER: true,
//open for read a file on user disk (input['type']='file')
_open_read_local: function (fio) {
let input = document.createElement('input');
input['type'] = 'file';
//no cancel handler, set a timer to 20s
fio._timer = window.setTimeout( ()=> {
if (!fio._select) return;
set_status('timeout on file selection for ' + fio.url);
fio._select = 2;
fio._timer = null;
}, 20000);
input.addEventListener("change", e => {
set_status('');
if (e.target.files[0]) {
fio._file = e.target.files[0];
fio._select = 0;
fio._pos = 0;
fio._size = fio._file.size;
//if more than 1G, load by 10M blocks
if ((fio._size>1000000000) && (fio._max_bsize<10000000)) fio._max_bsize = 10000000;
//if more than 1m, load by 500k blocks
else if ((fio._size>1000000) && (fio._max_bsize<500000)) fio._max_bsize = 500000;
GFIO.set_stats(fio._gfio, BigInt(0), BigInt(fio._size), 2, 0);
GFIO._load_block(fio);
} else {
_fio._select = 2;
}
window.clearTimeout(fio._timer);
fio._timer = null;
});
input.click();
set_status('Select input file for ' + fio.url);
return LIBGPAC.OK;
},
//open for write a file on user disk - showSaveFilePicker->createWitable, or blob->download if not supported
_open_write_local: function (fio) {
if (typeof window.showSaveFilePicker == 'undefined') {
if (GFIO.ALLOW_WRITE_DEFER) {
fio._defer_write = new Uint8Array();
fio._select = 0;
return LIBGPAC.OK;
}
do_log_err('File System Access API not supported');
return LIBGPAC.NOT_SUPPORTED;
}
//create file writer
GFIO.set_ready(fio._gfio, GFIO.WRITE_WAIT);
set_status('Select output file for ' + fio.url);
fio._writer = null;
const options = { suggestedName: fio.url, types: [ { description: fio.url, accept: { '*/*': [], } } ] };
window.showSaveFilePicker(options).then( filep => {
return filep.createWritable();
}).then( writer => {
fio._writer = writer;
fio._select = 0;
GFIO.set_ready(fio._gfio, GFIO.WRITE_READY);
}).catch(val => {
GFIO.set_ready(fio._gfio, GFIO.WRITE_CANCEL);
fio._select = 2;
});
return LIBGPAC.OK;
},
//open a remote file (fetch)
_open_read_remote: function (fio) {
let res = LIBGPAC.OK;
fio._controller = new AbortController();
let options = {
signal: fio._controller.signal,
mode: 'no-cors',
};
options.headers = {};
if (fio._pos) {
options.headers['range'] = 'bytes='+ fio._pos+'-';
}
options.headers['cross-origin-embedder-policy'] = 'credentialless';
options.headers['cross-origin-resource-policy'] = 'cross-origin';
GFIO.set_stats(fio._gfio, BigInt(0), BigInt(0), 2, 0);
fetch(fio.url, options).then((response) => {
if (response.ok) {
fio._select=0;
fio._fetch_wait=true;
fio._fetch = response.body.getReader();
fio._size = response.headers.get('Content-Length') || 0;
GFIO.set_stats(fio._gfio, BigInt(0), BigInt(fio._size), 2, 0);
} else {
GFIO.set_ready(fio._gfio, GFIO.WRITE_CANCEL);
fio._select=2;
}
})
.catch( (e) => {
fio._select=2;
GFIO.set_ready(fio._gfio, GFIO.WRITE_CANCEL);
res = LIBGPAC.URL_ERROR;
do_log_err('Fetch error: ' + e);
});
return res;
},
//write a remote file (fetch with readableStream if supported or fetch at end of file)
_open_write_remote: function (fio) {
let res = LIBGPAC.OK;
fio._defer_write = null;
if (!GFIO.supportsRequestStreams) {
if (GFIO.ALLOW_WRITE_DEFER) {
fio._defer_write = new Uint8Array();
fio._select=0;
return LIBGPAC.OK;
}
do_log_err('Fetch upload with readable streams not supported');
return LIBGPAC.NOT_SUPPORTED;
}
fio._controller = new AbortController();
GFIO.set_ready(fio._gfio, GFIO.WRITE_WAIT);
fio._upstream_dst=null;
fio._upstream = new ReadableStream({
start(controller) {
fio._select=0;
fio._upstream_dst = controller;
GFIO.set_ready(fio._gfio, GFIO.WRITE_READY);
},
pull(controller) {
fio._upstream_dst = controller;
},
cancel() {
GFIO.set_ready(fio._gfio, GFIO.WRITE_CANCEL);
}
});
let options = {
method: GFIO.UPLOAD_METHOD,
signal: fio._controller.signal,
body: fio._upstream,
duplex: 'half',
headers: { 'Content-Type': 'application/octet-stream' },
AllowHTTP1ForStreamingUpload: true
};
if (fio._pos) {
options.headers = {
'range': 'bytes='+ fio._pos+'-'
};
}
fetch(fio.url, options).then((response) => {
fio._select=0;
fio._size = fio._pos;
do_log('PUT got response ' + response.ok);
})
.catch( (e) => {
if (GFIO.ALLOW_WRITE_DEFER) {
fio._defer_write = new Uint8Array();
fio._select=0;
res = LIBGPAC.OK;
fio._upstream = null;
fio._upstream_dst = null;
fio._controller = null;
GFIO.set_ready(fio._gfio, GFIO.WRITE_READY);
} else {
GFIO.set_ready(fio._gfio, GFIO.WRITE_CANCEL);
res = LIBGPAC.NOT_SUPPORTED;
}
});
return res;
},
_close: function(fio) {
if (fio._stream) FS.close(fio._stream);
if (fio._writer) fio._writer.close();
if (fio._fetch) {
fio._controller.abort();
fio._controller = null;
}
if (fio._defer_write) {
if ((fio.url.indexOf('://')>0) && !fio.url.startsWith('file://')) {
let options = {
method: GFIO.UPLOAD_METHOD,
body: fio._defer_write,
headers: { 'Content-Type': 'application/octet-stream' },
};
if (fio._pos&&0) {
options.headers = {
'range': 'bytes='+ fio._pos+'-'
};
}
fetch(fio.url, options).then((response) => {
if (!response.ok) do_log('Error uploading ' + fio.url);
})
.catch( (e) => {
do_log('Error uploading ' + fio.url + ': ' + e);
});
} else {
var blob = new Blob([fio._defer_write.buffer], {type: 'application/octet-stream'});
var a = document.createElement("a");
var blob_url = window.URL.createObjectURL(blob);
a.href = blob_url;
a.download = fio.url;
a.click();
window.URL.revokeObjectURL(blob_url);
}
}
fio._stream = null;
fio._writer = null;
fio._fetch = null;
fio._defer_write = null;
fio._upstream = null;
fio._pos = 0;
},
//open callback from libgpac for GF_FileIO objects
open: function(fileio_ref, _url, _mode, out_e) {
let fio = GFIO.to_obj(fileio_ref);
let url = _url ? LIBGPAC.UTF8ToString(_url) : null;
let mode = LIBGPAC.UTF8ToString(_mode);
//close file
if (mode=='close') {
GFIO._close(fio);
LIBGPAC.setValue(out_e, LIBGPAC.OK, 'i32');
return 0;
}
//checks if file exists - we only probe for EM's FS for now
if (mode=='probe') {
let rurl = url.startsWith('gfio://') ? fio.url : url;
try {
let stats = FS.stat(rurl);
LIBGPAC.setValue(out_e, LIBGPAC.OK, 'i32');
} catch (e) {
LIBGPAC.setValue(out_e, LIBGPAC.URL_ERROR, 'i32');
}
return 0;
}
//add ref to this object
if (mode=='ref') {
fio._refs++;
return fileio_ref;
}
//remove ref from this object
if (mode=='unref') {
if (!fio._refs) return null;
fio._refs--;
if (fio._refs) return fileio_ref;
url = null;
}
//special mode URL to create a new GFIO from parent file name but without creating the file (used in dashing)
if (mode=="url") {
if (!url) return null;
let _newurl = LIBGPAC.url_cat(fio.url, url);
var new_gfio = GFIO.create( LIBGPAC.UTF8ToString(_newurl), 0);
LIBGPAC.free(newurl);
return new_gfio._gfio;
}
//no URL, close file
if (!url) {
GFIO._close(fio);
//TODO, cleanup of unused gfio refs ?
//if (!fio._refs) {}
return null;
}
mode = mode.replace('b', '');
mode = mode.replace('t', '');
mode = mode.replace('+', '');
//we don't support append modes
if ((mode != "r") && (mode != "w")) {
LIBGPAC.setValue(out_e, LIBGPAC.NOT_SUPPORTED, 'i32');
set_status('Unsupported open mode ' + mode);
return null;
}
//file handle not opened, we can use the current gfio
if (!fio._stream && !fio._file && !fio._fetch && !fio._upstream
&& (url.startsWith("gfio://") || (url == fio.url))
) {
var res=LIBGPAC.OK;
fio._pos = 0;
// URL is NOT an EM's FS file
if (fio._wrap_io) {
fio._select = 1;
if (mode=='r') {
if ((fio.url.indexOf('://')>0) && !fio.url.startsWith('file://')) {
res = GFIO._open_read_remote(fio);
} else {
res = GFIO._open_read_local(fio);
}
}
else if (mode=='w') {
if ((fio.url.indexOf('://')>0) && !fio.url.startsWith('file://')) {
res = GFIO._open_write_remote(fio);
} else {
res = GFIO._open_write_local(fio);
}
}
}
// URL is an EM's FS file
else {
try {
fio._stream = FS.open(fio.url, mode);
if (mode.startsWith('r')) {
let stats = FS.stat(fio.url);
fio._size = stats.size;
GFIO.set_stats(fio._gfio, BigInt(stats.size), BigInt(stats.size), 1, 0);
}
} catch (e) {
res = LIBGPAC.URL_ERROR;
}
}
LIBGPAC.setValue(out_e, res, 'i32');
if (res != LIBGPAC.OK) return null;
//return ourself
return fio._gfio;
}
//file handle already open (file is being opened twice), create a new gfio or check if we have already created one
//we only support this for local files
if (!fio._stream) {
LIBGPAC.setValue(out_e, LIBGPAC.NOT_SUPPORTED, 'i32');
return null;
}
let fio_ref = fio;
fio = null;
let no_concatenate=false;
GFIO.dict.forEach(a_gfio => {
if (fio) return;
if (a_gfio.url != url) return;
if (a_gfio._stream) {
no_concatenate = true;
} else {
fio = a_gfio;
}
});
if (!fio) {
let _path = null;
let path = null;
if (!url.startsWith("gfio://")) {
if (no_concatenate) {
path = url;
} else {
_path = LIBGPAC.url_cat(fio_ref.url, url);
path = LIBGPAC.UTF8ToString(_path);
}
} else {
path = fio_ref.url;
}
let res=LIBGPAC.OK;
try {
fio = GFIO.create(path, 0);
} catch (e) {
res = LIBGPAC.OUT_OF_MEM;
}
if (_path) LIBGPAC.free(_path);
LIBGPAC.setValue(out_e, res, 'i32');
if (res) return null;
}
try {
if (!url.startsWith("gfio://")) {
fio._stream = FS.open(fio.url, mode);
} else {
fio._stream = FS.open(fio_ref.url, mode);
}
} catch (e) {
//todo cleanup of old gfios
LIBGPAC.setValue(out_e, LIBGPAC.IO_ERR, 'i32');
return null;
}
if (mode.startsWith('r')) {
let stats = FS.stat(fio.url);
fio._size = stats.size;
GFIO.set_stats(fio._gfio, BigInt(stats.size), BigInt(stats.size), 1, 0);
}
return fio._gfio;
},
//seek callback from libgpac
seek: function(fileio, offset, whence) {
let fio = GFIO.to_obj(fileio);
if (fio._select==1) return LIBGPAC.OK;
if (fio._defer_write) return LIBGPAC.NOT_SUPPORTED;
if (fio._file || fio._writer || fio._fetch) {
let abspos = Number(offset);
if (whence==1) abspos += fio._pos;
else if (whence==2) abspos = fio._size;
if (fio._writer) {
fio._pos = abspos;
fio._writer.seek(abspos);
return LIBGPAC.OK;
}
if (abspos != fio._pos) {
fio._discard_next = true;
fio._pos = abspos;
fio._block_pos_in_file = abspos;
}
return LIBGPAC.OK;
}
try {
fio._pos = FS.llseek(fio._stream, Number(offset), whence);
} catch (e) {
return LIBGPAC.IO_ERR;
}
return LIBGPAC.OK;
},
//read callback from libgpac
read: function(fileio, buffer, bytes) {
let fio = GFIO.to_obj(fileio);
//IO being created, wait
if (fio._select) return 0;
//file system stream or fetch
if (fio._file || fio._fetch) {
let load_next=false;
if (bytes>fio._max_bsize) fio._max_bsize = bytes;
if (!fio._ab) {
//fetch
if (fio._fetch_wait) {
fio._fetch_wait = false;
fio._fetch.read().then( block => {
if (block.done) {
//done, force size in case it was not set
fio._size = fio._pos;
} else {
fio._ab = block.value; fio._block_pos = 0;
}
});
}
return 0;
}
if (fio._discard_next) {
fio._ab = null;
fio._block_pos = 0;
fio._discard_next = 0;
if (fio._fetch) {
fio._controller.abort();
fio._controller = null;
GFIO._open_read_remote(fio);
} else {
GFIO._load_block(fio);
}
return 0;
}
let remain = fio._ab.byteLength - fio._block_pos;
if (bytes > remain) {
load_next = true;
bytes = remain;
}
//copy array buffer
let src;
if (fio._fetch)
src = fio._ab.subarray(fio._block_pos, fio._block_pos+bytes);
else
src = new Uint8Array(fio._ab, fio._block_pos, bytes);
let dst = new Uint8Array(LIBGPAC.HEAPU8.buffer, buffer, bytes);
dst.set(src);
fio._pos += bytes;
if (load_next) {
fio._block_pos_in_file += fio._ab.byteLength;
fio._ab = null;
fio._block_pos = 0;
if (fio._file && (fio._pos < fio._size)) {
GFIO._load_block(fio);
}
if (fio._fetch) fio._fetch_wait = true;
} else {
fio._block_pos += bytes;
}
return bytes;
}
//Emscripten's FS
if (!fio._stream) return 0;
if (bytes + fio._pos > fio._size) {
bytes = fio._size - fio._pos;
}
fio._pos += bytes;
let ab = new Uint8Array(LIBGPAC.HEAPU8.buffer, buffer, bytes);
FS.read(fio._stream, ab, 0, bytes);
return bytes;
},
//write callback from libgpac
write: function(fileio, buffer, bytes) {
let fio = GFIO.to_obj(fileio);
//IO being created, wait
if (fio._select) return 0;
//defer write, cat arrays
if (fio._defer_write) {
let ab = new Uint8Array(LIBGPAC.HEAPU8.buffer, buffer, bytes);
let new_st = new Uint8Array(bytes + fio._defer_write.length);
new_st.set(fio._defer_write);
new_st.set(ab, fio._defer_write.length);
fio._defer_write = new_st;
return bytes;
}
//writable stream API
if (fio._writer) {
let ab = new Uint8Array(LIBGPAC.HEAPU8.buffer, buffer, bytes);
fio._writer.write( new Uint8Array(ab) );
return bytes;
}
//fetch+PUT+readableStream API
if (fio._upstream) {
let ab = new Uint8Array(LIBGPAC.HEAPU8.buffer, buffer, bytes);
fio._upstream_dst.enqueue(ab);
return bytes;
}
//EM's FS
if (!fio._stream) return 0;
fio._pos+=bytes;
if (fio._size < fio._pos) fio._size = fio._pos;
let ab = new Uint8Array(LIBGPAC.HEAPU8.buffer, buffer, bytes);
FS.write(fio._stream, ab, 0, bytes);
return bytes;
},
//tell callback from libgpac - we maintain position in resource ourself, so tell/eof are valid for all methods
tell: function(fileio) {
let fio = GFIO.to_obj(fileio);
if (fio._select) return BigInt(0);
return BigInt(fio._pos);
},
//eof callback from libgpac
eof: function(fileio) {
let fio = GFIO.to_obj(fileio);
if (fio._select==2) return 1;
if (fio._select) return 0;
if (fio._fetch && !fio._size) return 0;
if (fio._pos == fio._size) return 1;
return 0;
},
//get fileIO object from its userdata - for now we just use an index in the global dict that we reset upon gpac.on_done
to_obj: function(fileio) {
let udta = this.get_udta(fileio);
return this.dict[udta];
},
//create file IO from URL and mode (0: r/w, 1: r, 2: w), used by command line parser
create: function (url, mode) {
if (typeof this.wrap_new == 'undefined') {
//gf_fileio_new(url, udta, open, seek, read, write, tell, eof, printf)
this.wrap_new = LIBGPAC.cwrap('gf_fileio_new', 'number', ['string', 'number', 'number', 'number', 'number', 'number', 'number', 'number', 'number']);
this.wrap_del = LIBGPAC.cwrap('gf_fileio_del', '', ['number']);
this.get_udta = LIBGPAC.cwrap('gf_fileio_get_udta', 'number', ['number']);
this.get_url = LIBGPAC.cwrap('gf_fileio_url', 'string', ['number']);
this.tag_main = LIBGPAC.cwrap('gf_fileio_tag_main_thread', 'number', ['number']);
this.set_stats = LIBGPAC.cwrap('gf_fileio_set_stats', null, ['number', 'bigint', 'bigint', 'boolean', 'number']);
this.set_ready = LIBGPAC.cwrap('gf_fileio_set_write_state', null, ['number', 'number']);
//create function pointers
//
this._pf_open = LIBGPAC.addFunction(this.open, 'iiiii');
this._pf_seek = LIBGPAC.addFunction(this.seek, 'iiji');
this._pf_read = LIBGPAC.addFunction(this.read, 'iiii');
this._pf_write = LIBGPAC.addFunction(this.write, 'iiii');
this._pf_tell = LIBGPAC.addFunction(this.tell, 'ji');
this._pf_eof = LIBGPAC.addFunction(this.eof, 'ii');
}
let wrap_io = false;
if (url.startsWith('$(')) {
let select_end = url.indexOf(')');
url = url.substring(2, select_end);
wrap_io = true;
}
let fio = {
udta: this.dict.length,
url: url,
_wrap_io: wrap_io,
_pos: 0,
_size: 0,
_stream: null, //FS.open stream object
_file: null, //fileSelector file object
_refs: 0,
_select: 0,
_block_pos_in_file: 0,
_block_pos: 0, //amount of bytes read from block
_ab: null,
_max_bsize: GPAC.read_block_size, //min size, will grow to block_size of fin filter
_discard_next: null,
_timer: null,
_writer: null,
_fetch: null,
_fetch_wait: null,
gfio_url: function() {
return GFIO.get_url(fio._gfio);
}
};
this.dict.push(fio);
fio._gfio = this.wrap_new(url, fio.udta, this._pf_open, this._pf_seek, (mode==2) ? 0 : this._pf_read, (mode==1) ? 0 : this._pf_write, this._pf_tell, this._pf_eof, 0);
//only run gfio on main thread ?
this.tag_main(fio._gfio);
return fio;
},
//for file system read, loads next block from file
_load_block: function(fio) {
let max_bsize = fio._max_bsize;
let bsize = (fio._block_pos_in_file + max_bsize > fio._size) ? (fio._size - fio._block_pos_in_file) : max_bsize;
let blob = fio._file.slice(fio._block_pos_in_file, fio._block_pos_in_file + bsize);
let read = new FileReader();
read.onload = function(evt) {
fio._ab = evt.target.result;
};
read.readAsArrayBuffer(blob);
}
};
</script>
<!-- emscriptem module load -->
<script type='text/javascript'>
function prop_to_js(_p, p4cc) {
//TODO: for now we only dump the property to string, we don't map it to JS values
let res = LIBGPAC.props_dump_alloc(p4cc, _p, 0);
if (!res) return null;
let val = LIBGPAC.UTF8ToString(res);
LIBGPAC._free(res);
return val;
}
function load_libgpac(_lib, _lib)
{
let gpac_has_threads = LIBGPAC.cwrap('gpac_has_threads', 'number', []);
_lib.has_threads = gpac_has_threads();
_lib.in_logs = false;
//error codes
_lib.SCRIPT_INFO = 3;
_lib.PENDING_PACKET = 2;
_lib.EOS = 1;
_lib.OK = 0;
_lib.BAD_PARAM = -1;
_lib.OUT_OF_MEM = -2;
_lib.IO_ERR = -3;
_lib.NOT_SUPPORTED = -4;
_lib.CORRUPTED_DATA = -5;
_lib.SCRIPT_ERROR = -8;
_lib.BUFFER_TOO_SMALL = -9;
_lib.NON_COMPLIANT_BITSTREAM = -10;
_lib.FILTER_NOT_FOUND = -11;
_lib.URL_ERROR = -12;
_lib.SERVICE_ERROR = -13;
_lib.REMOTE_SERVICE_ERROR = -14;
_lib.STREAM_NOT_FOUND = -15;
_lib.URL_REMOVED = -16;
_lib.IP_ADDRESS_NOT_FOUND = -40;
_lib.IP_CONNECTION_FAILURE = -41;
_lib.IP_NETWORK_FAILURE = -42;
_lib.IP_CONNECTION_CLOSED = -43;
_lib.IP_NETWORK_EMPTY = -44;
_lib.IP_UDP_TIMEOUT = -46;
_lib.AUTHENTICATION_FAILURE = -50;
_lib.NOT_READY = -51;
_lib.INVALID_CONFIGURATION = -52;
_lib.NOT_FOUND = -53;
_lib.PROFILE_NOT_SUPPORTED = -54;
_lib.REQUIRES_NEW_INSTANCE = -56;
_lib.FILTER_NOT_SUPPORTED = -57;
//functions
_lib.init = LIBGPAC.cwrap('gf_sys_init', 'number', ['number', 'string']);
_lib.uninit = LIBGPAC.cwrap('gf_sys_close', null, []);
_lib.version = LIBGPAC.cwrap('gf_gpac_version', 'string', []);
_lib.copyright = LIBGPAC.cwrap('gf_gpac_copyright', 'string', []);
_lib.url_cat = LIBGPAC.cwrap('gf_url_concatenate', 'number', ['string', 'string']);
_lib.free = LIBGPAC.cwrap('gf_url_free', null, ['number']);
_lib.log_set_tools_levels = LIBGPAC.cwrap('gf_log_set_tools_levels', 'number', ['string', 'number']);
_lib.e2s = LIBGPAC.cwrap('gf_error_to_string', 'string', ['number']);
//load basic filter session api
_lib.fs_new_defaults = LIBGPAC.cwrap('gf_fs_new_defaults', 'number', ['number']);
_lib.fs_del = LIBGPAC.cwrap('gf_fs_del', null, ['number']);
_lib.fs_print_stats = LIBGPAC.cwrap('gf_fs_print_stats', null, ['number']);
_lib.fs_print_connections = LIBGPAC.cwrap('gf_fs_print_connections', null, ['number']);
_lib.fs_load_filter = LIBGPAC.cwrap('gf_fs_load_filter', 'number', ['number', 'string', 'number']);
_lib.fs_load_source = LIBGPAC.cwrap('gf_fs_load_source', 'number', ['number', 'string', 'string', 'string', 'number']);
_lib.fs_load_destination = LIBGPAC.cwrap('gf_fs_load_destination', 'number', ['number', 'string', 'string', 'string', 'number']);
_lib.fs_run = LIBGPAC.cwrap('gf_fs_run', 'number', ['number']);
_lib.fs_last_task = LIBGPAC.cwrap('gf_fs_is_last_task', 'number', ['number']);
_lib.filter_get_name = LIBGPAC.cwrap('gf_filter_get_name', 'string', ['number']);
_lib.filter_reconnect_output = LIBGPAC.cwrap('gf_filter_reconnect_output', 'number', ['number','number']);
_lib.filter_probe_link = LIBGPAC.cwrap('gf_filter_probe_link', 'number', ['number','number','string', 'number']);
_lib.filter_get_destinations = LIBGPAC.cwrap('gf_filter_get_possible_destinations', 'number', ['number','number', 'number']);
_lib.filter_get_ipid_count = LIBGPAC.cwrap('gf_filter_get_ipid_count', 'number', ['number']);
_lib.filter_get_ipid = LIBGPAC.cwrap('gf_filter_get_ipid', 'number', ['number','number']);
_lib.filter_get_opid_count = LIBGPAC.cwrap('gf_filter_get_opid_count', 'number', ['number']);
_lib.filter_get_opid = LIBGPAC.cwrap('gf_filter_get_opid', 'number', ['number','number']);
_lib.filter_is_sink = LIBGPAC.cwrap('gf_filter_is_sink', 'number', ['number']);
_lib.filter_is_source = LIBGPAC.cwrap('gf_filter_is_source', 'number', ['number']);
_lib.filter_get_max_extra_input_pids = LIBGPAC.cwrap('gf_filter_get_max_extra_input_pids', 'number', ['number']);
_lib.filter_pid_get_property = LIBGPAC.cwrap('gf_filter_pid_get_property', 'number', ['number', 'number']);
_lib.filter_pid_get_property_str = LIBGPAC.cwrap('gf_filter_pid_get_property', 'number', ['number', 'string']);
_lib.filter_pid_enum_properties = LIBGPAC.cwrap('gf_filter_pid_enum_properties', 'number', ['number', 'number', 'number', 'string']);
_lib.props_4cc_name = LIBGPAC.cwrap('gf_props_4cc_get_name', 'string', ['number']);
_lib.props_dump_alloc = LIBGPAC.cwrap('gf_props_dump_alloc', 'number', ['number','number','number']);
_lib.filter_pid_get_props = (pid) => {
props = [];
const p_idx = LIBGPAC._malloc(LIBGPAC.HEAP32.BYTES_PER_ELEMENT);
const p_4cc = LIBGPAC._malloc(LIBGPAC.HEAP32.BYTES_PER_ELEMENT);
const p_name = LIBGPAC._malloc(LIBGPAC.HEAP32.BYTES_PER_ELEMENT);
LIBGPAC.HEAP32.set([0], p_idx/LIBGPAC.HEAP32.BYTES_PER_ELEMENT);
while (1) {
let _p = _lib.filter_pid_enum_properties(pid, p_idx, p_4cc, p_name);
if (!_p) break;
let prop_4cc = LIBGPAC.getValue(p_4cc, 'i32');
let c_str = LIBGPAC.getValue(p_name, "i32"); //const, no need to free
let prop_name = c_str ? LIBGPAC.UTF8ToString(c_str) : null;
p = {};
p.name = prop_4cc ? _lib.props_4cc_name(prop_4cc) : prop_name;
p.value = prop_to_js(_p, prop_4cc);
if (p.value)
props.push(p);
}
LIBGPAC._free(p_idx);
LIBGPAC._free(p_4cc);
LIBGPAC._free(p_name);
return props;
};
}
var GPAC = {
on_done: function() {
LIBGPAC.gpac_set_console(3);
set_status('');
GPAC.running = false;
GPAC.do_log = false;
//in case we had an exception, force a gf_sys_close
LIBGPAC.uninit();
if (GPAC.restore_worker) {
LIBGPAC.gpac_worker = true;
GPAC.restore_worker = false;
}
GFIO.dict.forEach(fio => {
if (fio._timer) {
window.clearTimeout(fio._timer);
fio._timer=null;
}
});
GFIO.dict = [];
animElement.stop();
cmdElement.readonly = false;
//restore emscripten stack
if (GPAC.stack != null) {
var stack_init = GPAC.stack;
GPAC.stack = null;
function restore_stack() {
LIBGPAC.stackRestore(stack_init);
}
//we may be called asynchronously here if emscripten main loop is used, so restore stack on main thread only
setTimeout(restore_stack, 1);
}
},
running: false,
no_log: true,
stack: null,
anim: animElement,
restore_worker: false,
read_block_size: 10000
};
function displayStatus(text, busy = false, progress = null){
statusElement.innerHTML = text;
progressTxt.nodeValue = progress !== null ? `${Math.floor(val)} %` : '';
if (busy){
animElement.start();
} else {
animElement.stop();
}
}
function check_threading() {
const threaded = LIBGPAC.has_threads;
const threadedOnly = [...document.getElementsByClassName('threaded')];
threadedOnly.forEach(el => el.hidden = !threaded);
const thStatus = document.getElementById('threading_status');
threadingElement.checked = threaded;
if (window.location.href.indexOf('.gpac.io')<0) {
threadingElement.disabled = true;
} else {
threadingElement.onchange = () => {
const hostname = threaded ? 'wasm.gpac.io' : 'wasmth.gpac.io';
window.location.href = `https://${hostname}/`;
console.log('Reloading to ' + window.location.href);
}
}
}
const LIBGPAC = {
preRun: [
// function() { LIBGPAC.ENV.SDL_EMSCRIPTEN_KEYBOARD_ELEMENT = "#canvas"; }
],
postRun: [
],
print: (function() {
if (outputElement) outputElement.innerHTML = ''; // clear browser cache
return function(text) {
if (arguments.length > 1) text = Array.prototype.slice.call(arguments).join(' ');
// These replacements are necessary if you render to raw HTML
text = text.replace(/&/g, "&amp;");
text = text.replace(/</g, "&lt;");
text = text.replace(/>/g, "&gt;");
text = text.replace(/\n/g, '<br>', 'g');
text = text.replace(/\t/g, '&nbsp;&nbsp;&nbsp;&nbsp;');
// handle \r
var prevPos = 0;
var output = '';
for (var i = 0; i < text.length; i++) {
if (text.charCodeAt(i) == 13) {
output += text.substring(prevPos, i);
prevPos = i + 1;
}
}
// convert ANSI colors to HTML
text = ansiHTML(text);
if (GPAC.no_log)
console.log(text);
else if (outputElement) {
outputElement.innerHTML += text + "<br>";
outputElement.scrollTop = outputElement.scrollHeight; // focus on bottom
}
};
})(),
printErr: function(text) {
if (LIBGPAC.in_logs)
console.log(text);
else
LIBGPAC.print(text);
},
canvas: (function() {
var canvas = document.getElementById('canvas');
// As a default initial behavior, pop up an alert when webgl context is lost. To make your
// application robust, you may want to override this behavior before shipping!
// See http://www.khronos.org/registry/webgl/specs/latest/1.0/#5.15.2
canvas.addEventListener("webglcontextlost", function(e) { alert('WebGL context lost. You will need to reload the page.'); e.preventDefault(); }, false);
return canvas;
})(),
setStatus: function(text) {
if (!LIBGPAC.setStatus.last) LIBGPAC.setStatus.last = { time: Date.now(), text: '' };
if (text === LIBGPAC.setStatus.last.text) return;
var m = text.match(/([^(]+)\((\d+(\.\d+)?)\/(\d+)\)/);
var now = Date.now();
if (m && now - LIBGPAC.setStatus.last.time < 30) return; // if this is a progress update, skip it if too soon
LIBGPAC.setStatus.last.time = now;
LIBGPAC.setStatus.last.text = text;
const busy = !!m;
let progress = null;
if (busy) {
text = m[1];
progress = parseInt(m[2])*100 / parseInt(m[4]);
}
displayStatus(text, busy, progress);
},
totalDependencies: 0,
monitorRunDependencies: function(left) {
this.totalDependencies = Math.max(this.totalDependencies, left);
LIBGPAC.setStatus(left ? 'Preparing... (' + (this.totalDependencies-left) + '/' + this.totalDependencies + ')' : 'All downloads complete.');
},
onRuntimeInitialized: function() {
FS = LIBGPAC['FS'];
load_libgpac(LIBGPAC, LIBGPAC);
GPAC.toggle_reporting = function(value) {
const gpac_em_sig_handler = LIBGPAC.cwrap('gpac_em_sig_handler', null, ['number']);
gpac_em_sig_handler(value);
};
document.addEventListener("keydown", (event) => {
if (event.ctrlKey) {
switch (event.keyCode) {
case 76: // Ctrl+L
case 65: // Ctrl+A
case 69: // Ctrl+E
event.preventDefault();
break;
}
}
})
//mount IDBFS
FS.mkdir('/idbfs');
FS.mount(LIBGPAC['IDBFS'], {}, '/idbfs');
FS.syncfs(true, () => {
if (arguments.length) {
err('Failed to sync IDBFS: ' + arguments[0] + ' - will use Emscripten $HOME for config');
} else {
load_history();
}
});
check_threading();
onGpacReady();
},
//do not run until we have something to run
noInitialRun: true,
//do not exit runtime on exit()
noExitRuntime: true,
//GPAC-specific extensions of the module object, only used by gpac_main.c (NOT USED by libgpac functions)
//callback function called by gpac_main when exiting
gpac_done: function(code) {
if (code) do_log('(exit code ' + code +')');
GPAC.on_done();
com_exit();
},
//set console function called by gpac_main
//code is 1 for console clear, 2 for console save and 3 for console restore (color codes are not forwarded)
gpac_set_console: function(code) {
if (!outputElement) return;
switch (code) {
//clear
case 1:
outputElement.value = '';
return;
//save
case 2:
if (console_bck.length) return;
console_bck = outputElement.value;
outputElement.value = '';
return;
//restore
case 3:
if (!console_bck) return;
outputElement.value = console_bck;
console_bck = '';
return;
}
},
//worker flag used by gpac_main to launch as worker
gpac_worker: false,
//boolean flag disabling/enabling fetch() - if undefined or not false, fetch() is enabled
gpac_fetch: true,
//fetch mode or cors
gpac_fetch_mode: 'cors',
//extra headers to add to fetch requests
gpac_extra_headers: {}
};
displayStatus('Downloading...', true);
window.onerror = function(event) {
// TODO: do not warn on ok events like simulating an infinite loop or exitStatus
displayStatus('Exception thrown, see JavaScript console');
if (LIBGPAC){
LIBGPAC.setStatus = function(text) {
if (text) console.error('[post-exception status] ' + text);
};
}
};
</script>
<!-- load module - default emscripten compilation is with MODULARIZE, EXPORT_ES6 and export name "libgpac" -->
<script type="module" async>
import libgpac from './gpac.js';
window.addEventListener('load', function() {
libgpac(LIBGPAC);
});
</script>
</body>
</html>