Play file in browser or with external helper application #34
|
@ -10,7 +10,7 @@ set urlbase [cgi_get base ""]
|
||||||
|
|
||||||
# Default to just downloading the raw file.
|
# Default to just downloading the raw file.
|
||||||
set url $file
|
set url $file
|
||||||
set mime "video/ts"
|
set mime "video/mp2t"
|
||||||
|
|
||||||
if {[string match {*.ts} $file]} {
|
if {[string match {*.ts} $file]} {
|
||||||
if {![catch {set ts [ts fetch $file]}]} {
|
if {![catch {set ts [ts fetch $file]}]} {
|
||||||
|
|
|
@ -13,10 +13,14 @@ if {$file == 0} exit
|
||||||
|
|
||||||
set sz [pretty_size [file size $file]]
|
set sz [pretty_size [file size $file]]
|
||||||
|
|
||||||
|
set flags {}
|
||||||
|
set url ""
|
||||||
|
|
||||||
|
# assumption: the type is only ts if fetch has already been checked
|
||||||
if {$type eq "ts"} {
|
if {$type eq "ts"} {
|
||||||
require epg.class ts.class
|
require epg.class ts.class
|
||||||
|
|
||||||
set ts [ts fetch $file]
|
set ts [ts fetch $file 1]
|
||||||
|
|
||||||
# Causes other series information to be automatically populated
|
# Causes other series information to be automatically populated
|
||||||
set epname [$ts episode_name]
|
set epname [$ts episode_name]
|
||||||
|
@ -199,7 +203,7 @@ eval_plugins browsetsfile
|
||||||
|
|
||||||
puts "<tr>
|
puts "<tr>
|
||||||
<th>Flags</th>
|
<th>Flags</th>
|
||||||
<td>[$ts get flags]</td>
|
<td>[set flags [$ts get flags]]</td>
|
||||||
</tr>
|
</tr>
|
||||||
"
|
"
|
||||||
|
|
||||||
|
@ -215,9 +219,6 @@ if {[$ts get bookmarks]} {
|
||||||
"
|
"
|
||||||
}
|
}
|
||||||
|
|
||||||
puts "
|
|
||||||
</table>
|
|
||||||
"
|
|
||||||
puts "<div class=hidden id=file>$file</div>"
|
puts "<div class=hidden id=file>$file</div>"
|
||||||
puts {
|
puts {
|
||||||
<script type=text/javascript>
|
<script type=text/javascript>
|
||||||
|
@ -256,12 +257,12 @@ $('img.rollimg').hover(
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
}
|
}
|
||||||
exit
|
|
||||||
}
|
}
|
||||||
|
|
||||||
# Otherwise, for a general file.
|
# Otherwise, for a general file.
|
||||||
|
|
||||||
puts "
|
if {$type ne "ts"} {
|
||||||
|
puts "
|
||||||
<table class=keyval>
|
<table class=keyval>
|
||||||
<tr>
|
<tr>
|
||||||
<th>File</th>
|
<th>File</th>
|
||||||
|
@ -269,20 +270,33 @@ puts "
|
||||||
</tr><tr>
|
</tr><tr>
|
||||||
<th>Size</th>
|
<th>Size</th>
|
||||||
<td>$sz</td>
|
<td>$sz</td>
|
||||||
</tr><tr>
|
</tr>"
|
||||||
|
}
|
||||||
|
|
||||||
|
set hasffmpeg 0
|
||||||
|
if {$type ne "ts" || ("ODEncrypted" ni $flags && $url eq "") } {
|
||||||
|
puts "<tr>
|
||||||
<th>Info</th>
|
<th>Info</th>
|
||||||
<td class=pre id=ffmpeg>
|
<td class=pre id=ffmpeg>
|
||||||
<img src=/img/spin.gif><i>Loading...</i>
|
<img src=/img/spin.gif><i>Loading...</i>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>"
|
||||||
</table>
|
set hasffmpeg 1
|
||||||
"
|
} elseif {$type eq "ts" && $url ne ""} {
|
||||||
|
puts [format {
|
||||||
set url "/browse/ffmpeg.jim?file=[cgi_quote_url $file]"
|
<script type="text/javascript">
|
||||||
puts { <script type="text/javascript"> }
|
$('#playDL').attr('href','%s').enable();
|
||||||
puts "var url = \"$url\";"
|
</script> } $url]
|
||||||
puts {
|
|
||||||
$('#ffmpeg').load(url);
|
|
||||||
</script>
|
|
||||||
}
|
}
|
||||||
|
puts "
|
||||||
|
</table>
|
||||||
|
"
|
||||||
|
|
||||||
|
if {$hasffmpeg} {
|
||||||
|
set url "/browse/ffmpeg.jim?file=[cgi_quote_url $file]"
|
||||||
|
puts [format {
|
||||||
|
<script type="text/javascript">
|
||||||
|
var url = "%s";
|
||||||
|
$('#ffmpeg').load(url, function() { $('#play, #playDL').enable(); });
|
||||||
|
</script> } $url]
|
||||||
|
}
|
||||||
|
|
|
@ -21,7 +21,7 @@ header
|
||||||
set nicesplice [system pkginst nicesplice]
|
set nicesplice [system pkginst nicesplice]
|
||||||
|
|
||||||
set ignore {.nts .thm .hmi}
|
set ignore {.nts .thm .hmi}
|
||||||
set include {.ts .avi .mpg .mpeg .wmv .mkv .mp3 .mp4 .mov .hmt .m4v .m4a}
|
set include {.ts .avi .mpg .mpeg .wmv .mkv .mp3 .mp4 .mov .hmt .m4v .m4a .webm}
|
||||||
|
|
||||||
if {![dict exists $env SCRIPT_NAME]} {
|
if {![dict exists $env SCRIPT_NAME]} {
|
||||||
set env(SCRIPT_NAME) ""
|
set env(SCRIPT_NAME) ""
|
||||||
|
@ -139,6 +139,14 @@ proc entry {file} {{i 0}} {
|
||||||
set img Video_Failed
|
set img Video_Failed
|
||||||
}
|
}
|
||||||
set omenu opt
|
set omenu opt
|
||||||
|
if {[$ts get definition] eq ""} {
|
||||||
|
set type gen
|
||||||
|
set ts 0
|
||||||
|
set img Video_Other
|
||||||
|
set omenu oopt
|
||||||
|
} else {
|
||||||
|
set omenu opt
|
||||||
|
}
|
||||||
if {[file exists "${base}.thm"]} { set thmok 1 }
|
if {[file exists "${base}.thm"]} { set thmok 1 }
|
||||||
} elseif {$ext eq ".hmt"} {
|
} elseif {$ext eq ".hmt"} {
|
||||||
if {[file exists "${base}.ts"]} { return }
|
if {[file exists "${base}.ts"]} { return }
|
||||||
|
@ -207,8 +215,8 @@ proc entry {file} {{i 0}} {
|
||||||
|
|
||||||
# Indexed
|
# Indexed
|
||||||
set dlna 0
|
set dlna 0
|
||||||
if {$::dlnaok && $::model eq "HDR" && [llength [
|
if {$::dlnaok && $::model eq "HDR" &&
|
||||||
system dlnaurl [file normalize $file]]]} {
|
[llength [system dlnaurl $file]]} {
|
||||||
icon "/img/dlna.png" "Indexed by DLNA Server"
|
icon "/img/dlna.png" "Indexed by DLNA Server"
|
||||||
set dlna 1
|
set dlna 1
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,62 @@
|
||||||
|
#!/mod/bin/jimsh
|
||||||
|
|
||||||
|
package require cgi
|
||||||
|
source /mod/webif/lib/setup
|
||||||
|
require system.class
|
||||||
|
require ts.class
|
||||||
|
|
||||||
|
set file [cgi_get file]
|
||||||
|
set urlbase [cgi_get base ""]
|
||||||
|
set duration [cgi_get duration 1]
|
||||||
|
set fmts [split [cgi_get fmts ""] ","]
|
||||||
|
set vc [cgi_get vc ""]
|
||||||
|
|
||||||
|
# Default to just downloading the raw file.
|
||||||
|
set url $file
|
||||||
|
|
||||||
|
# Prefer to use DLNA server ... (necessary if encrypted)
|
||||||
|
set dlna [system dlnaurl $url $urlbase]
|
||||||
|
if {[llength $dlna]} {
|
||||||
|
set url [lindex $dlna 0]
|
||||||
|
} elseif {[regexp {^(https?://(.+:.*@)?[[:alnum:].]+(:[[:digit:]]+)?)/} $urlbase x y]} {
|
||||||
|
set url "$y$url"
|
||||||
|
} else {
|
||||||
|
set url "http://[system ip]$url"
|
||||||
|
}
|
||||||
|
|
||||||
|
if {[file extension $file] in {.ts .TS}} {
|
||||||
|
if {![catch {set ts [ts fetch $file]}] && $ts != 0} {
|
||||||
|
set duration [$ts duration 1]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
set file [file tail $file]
|
||||||
|
set playlist [file tempfile "[env "TMPDIR" [env "TMP" "/tmp"]]/playXXXXXX"]
|
||||||
|
set pl ""
|
||||||
|
try {
|
||||||
|
set pl [open $playlist w]
|
||||||
|
$pl puts "#EXTM3U"
|
||||||
|
$pl puts "#EXTINF:$duration,$file"
|
||||||
|
$pl puts "#PLAYLIST:$file"
|
||||||
|
$pl puts $url
|
||||||
|
} finally {
|
||||||
|
if {$pl ne ""} {
|
||||||
|
$pl close
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
httpheader "application/x-mpegurl" 0 [list \
|
||||||
|
"Content-Disposition" "attachment; filename=\"[file rootname $file].m3u\"" \
|
||||||
|
"Content-Length" "[file size $playlist]" \
|
||||||
|
]
|
||||||
|
set pl ""
|
||||||
|
try {
|
||||||
|
set pl [open $playlist r]
|
||||||
|
$pl copyto stdout
|
||||||
|
} finally {
|
||||||
|
if {$pl ne ""} {
|
||||||
|
$pl close
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch {file delete $playlist}
|
||||||
|
|
|
@ -10,7 +10,7 @@ var plugins = {
|
||||||
dmenu_prepare: {}
|
dmenu_prepare: {}
|
||||||
};
|
};
|
||||||
|
|
||||||
// pattern matches directory path prefix
|
// pattern matches directory path prefix and suffix
|
||||||
var pathre = /.*\/|\.[^.]*$/g;
|
var pathre = /.*\/|\.[^.]*$/g;
|
||||||
|
|
||||||
// IDs of size, img elements for folders use RFC4648 s5 encoding of name
|
// IDs of size, img elements for folders use RFC4648 s5 encoding of name
|
||||||
|
@ -787,12 +787,14 @@ $('img.doopt').contextMenu(
|
||||||
// Disable items which are not yet implemented.
|
// Disable items which are not yet implemented.
|
||||||
$('#optmenu').disableContextMenuItems('#title');
|
$('#optmenu').disableContextMenuItems('#title');
|
||||||
|
|
||||||
var $buttons = {
|
var $buttons = [
|
||||||
"Close" : function() {$(this).dialog('close');}
|
{ id: 'close',
|
||||||
};
|
text: 'Close',
|
||||||
var $buttonsp = $.extend(
|
click: function() {$(this).dialog('close');}},
|
||||||
{"Play" : function() { doplay(); }},
|
{ id: 'play',
|
||||||
$buttons);
|
text: 'Play',
|
||||||
|
click: function() { doplay(this); }}
|
||||||
|
];
|
||||||
|
|
||||||
// Create reusable dialogue.
|
// Create reusable dialogue.
|
||||||
var $dialog = $('#dialogue').dialog({
|
var $dialog = $('#dialogue').dialog({
|
||||||
|
@ -806,16 +808,56 @@ var $dialog = $('#dialogue').dialog({
|
||||||
'<img src="/img/spin.gif">Retrieving data...'); }
|
'<img src="/img/spin.gif">Retrieving data...'); }
|
||||||
});
|
});
|
||||||
|
|
||||||
function doplay()
|
/* insert button-like Download link before Play */
|
||||||
|
$('#play').before(function(i){
|
||||||
|
var dl = document.createElement('a');
|
||||||
|
dl.setAttribute('class', this.className);
|
||||||
|
dl.id = this.id + 'DL';
|
||||||
|
dl.innerHTML = 'Download';
|
||||||
|
return dl;
|
||||||
|
});
|
||||||
|
|
||||||
|
function doplay(it)
|
||||||
{
|
{
|
||||||
var file = $dialog.attr('file');
|
var file = $dialog.attr('file');
|
||||||
var type = $dialog.attr('type');
|
var type = $dialog.attr('type');
|
||||||
|
|
||||||
disableall();
|
var duration = 0;
|
||||||
|
var fmts = "";
|
||||||
|
var vc = ""
|
||||||
|
var ff = $('#ffmpeg')[0];
|
||||||
|
|
||||||
|
if (ff) {
|
||||||
|
/* extract duration, container and video codec from ffmpeg output */
|
||||||
|
ff = ff.innerHTML;
|
||||||
|
var match = /Duration:\s+([0-9.:]+),/.exec(ff);
|
||||||
|
if (match && match[1])
|
||||||
|
duration = (new Date('1970-01-01T' + match[1] + 'Z')).getTime()/1000;
|
||||||
|
match = /Input #0,\s+([-A-Za-z0-9_,]+),\s/.exec(ff);
|
||||||
|
if (match && match[1]) fmts = match[1];
|
||||||
|
match = /Stream #.+\sVideo:\s+([-A-Za-z0-9_]+)\s/.exec(ff);
|
||||||
|
if (match && match[1]) vc = match[1];
|
||||||
|
}
|
||||||
|
|
||||||
window.location = '/play/play.jim?' +
|
fmts = /mp4|webm/.exec(fmts);
|
||||||
'dir=' + encodeURIComponent(dir) +
|
if (fmts && fmts[0])
|
||||||
'&file=' + encodeURIComponent(file);
|
vc = /h264|av1|vp9/.exec(vc);
|
||||||
|
else
|
||||||
|
vc = null;
|
||||||
|
|
||||||
|
if (vc && vc[0]) {
|
||||||
|
/* base on page address to handle client on external network, etc */
|
||||||
|
var hh = new URL(file, window.location.href);
|
||||||
|
window.open(hh.href, 'WebIf_Player');
|
||||||
|
} else {
|
||||||
|
window.location = '/browse/play.jim?' +
|
||||||
|
'dir=' + encodeURIComponent(dir) +
|
||||||
|
'&base=' + encodeURI(window.location.hostname) +
|
||||||
|
'&duration=' + duration +
|
||||||
|
'&file=' + encodeURIComponent(file);
|
||||||
|
}
|
||||||
|
|
||||||
|
$(it).dialog('close');
|
||||||
}
|
}
|
||||||
|
|
||||||
// Bind dialogue open to filenames.
|
// Bind dialogue open to filenames.
|
||||||
|
@ -834,11 +876,24 @@ $('a.bf').click(function(e) {
|
||||||
$dialog.attr('file', file);
|
$dialog.attr('file', file);
|
||||||
$dialog.attr('type', type);
|
$dialog.attr('type', type);
|
||||||
|
|
||||||
if (type == 'ts' &&
|
$('#playDL').attr('download', file.replace(/.*\//, ''));
|
||||||
(opt.attr('odencd') == 0 || opt.attr('dlna') == 1))
|
|
||||||
$dialog.dialog("option", "buttons", $buttonsp);
|
if (type == 'ts') {
|
||||||
else
|
if (opt.attr('odencd') != 0) {
|
||||||
$dialog.dialog("option", "buttons", $buttons);
|
/* encrypted: link to be enabled once populated */
|
||||||
|
$('#playDL').disable();
|
||||||
|
/* ... but if no DLNA never Play */
|
||||||
|
if (opt.attr('dlna') != 1) $('#play').disable();
|
||||||
|
} else {
|
||||||
|
/* link unencrypted file directly */
|
||||||
|
$('#playDL').attr('href', file);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
/* generic: enable Play once media file is parsed */
|
||||||
|
$('#play').disable();
|
||||||
|
$('#playDL').attr('href', file);
|
||||||
|
}
|
||||||
|
|
||||||
$dialog.dialog('open');
|
$dialog.dialog('open');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -35,3 +35,13 @@ input.uint8_t
|
||||||
width: 6ch;
|
width: 6ch;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* a link that looks like a button */
|
||||||
|
button + a.ui-button
|
||||||
|
{
|
||||||
|
margin-right: 0.4em;
|
||||||
|
}
|
||||||
|
.ui-button:link
|
||||||
|
{
|
||||||
|
background-color: rgb(254, 206, 47) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
|
@ -33,16 +33,24 @@ if {![exists -proc require]} {
|
||||||
exit
|
exit
|
||||||
}
|
}
|
||||||
|
|
||||||
proc httpheader {{type "text/html"} {cache 0} {extra ""}} {{done 0}} {
|
proc httpheader {{type "text/html"} {cache 0} {extra {}}} {{done 0}} {
|
||||||
if {$done} return
|
if {$done} return
|
||||||
if {!$cache} {
|
if {!$cache} {
|
||||||
puts -nonewline "Content-Type: $type; charset=\"UTF-8\"; no-cache\r\n"
|
set hdr [dict create \
|
||||||
puts -nonewline "Expires: -1\r\n"
|
"Content-Type" "$type; charset=\"UTF-8\"; no-cache" \
|
||||||
puts -nonewline "Connection: close\r\n"
|
"Expires" "-1" \
|
||||||
puts -nonewline "Pragma: no-cache\r\n"
|
"Connection" "close" \
|
||||||
puts -nonewline "Cache-Control: no-cache\r\n"
|
"Pragma" "no-cache" \
|
||||||
|
"Cache-Control" "no-cache"]
|
||||||
} else {
|
} else {
|
||||||
puts -nonewline "Content-Type: $type; charset=\"UTF-8\"\r\n"
|
set hdr [dict create "Content-Type" "$type; charset=\"UTF-8\""]
|
||||||
|
}
|
||||||
|
if {![catch {dict size $extra}]} {
|
||||||
|
set hdr [dict merge $hdr $extra]
|
||||||
|
set extra ""
|
||||||
|
}
|
||||||
|
dict for {k v} $hdr {
|
||||||
|
puts -nonewline "$k: $v\r\n"
|
||||||
}
|
}
|
||||||
if {$extra ne ""} { puts -nonewline "$extra" }
|
if {$extra ne ""} { puts -nonewline "$extra" }
|
||||||
puts -nonewline "\r\n"
|
puts -nonewline "\r\n"
|
||||||
|
|
|
@ -269,7 +269,11 @@ proc {system dlnadb} {} {
|
||||||
}
|
}
|
||||||
|
|
||||||
proc {system _dlnaurl} {file urlbase} {
|
proc {system _dlnaurl} {file urlbase} {
|
||||||
set mime "video/ts"
|
set mime "video/mp2t"
|
||||||
|
set nfile $file
|
||||||
|
if {![catch {set nfile [file normalize $file]}]} {
|
||||||
|
set file $nfile
|
||||||
|
}
|
||||||
if {[catch {set db [sqlite3.open [system dlnadb]]}]} {
|
if {[catch {set db [sqlite3.open [system dlnadb]]}]} {
|
||||||
return {}
|
return {}
|
||||||
}
|
}
|
||||||
|
@ -278,7 +282,10 @@ proc {system _dlnaurl} {file urlbase} {
|
||||||
from tblresource join tblmedia using (mediaid)
|
from tblresource join tblmedia using (mediaid)
|
||||||
where localurl = '%s'} $file]
|
where localurl = '%s'} $file]
|
||||||
if {[llength $muri]} {
|
if {[llength $muri]} {
|
||||||
lassign [lindex $muri 0] x mime x xuri
|
lassign [lindex $muri 0] x maybemime x xuri
|
||||||
|
if {$maybemime ne "video/ts"} {
|
||||||
|
set mime $maybemime
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
# Try for partially linked entry
|
# Try for partially linked entry
|
||||||
set muri [$db query {
|
set muri [$db query {
|
||||||
|
@ -297,7 +304,7 @@ proc {system _dlnaurl} {file urlbase} {
|
||||||
return [list $url $mime]
|
return [list $url $mime]
|
||||||
}
|
}
|
||||||
|
|
||||||
proc {system dlnaurl} {file {urlbase ""}} {
|
proc {system dlnaurl} {file {urlbase "127.0.0.1"}} {
|
||||||
if {$urlbase eq ""} { set urlbase [system ip] }
|
if {$urlbase eq ""} { set urlbase [system ip] }
|
||||||
set retries 5
|
set retries 5
|
||||||
set ret {}
|
set ret {}
|
||||||
|
|
|
@ -331,7 +331,7 @@ ts method setgenre {newgenre} {
|
||||||
}
|
}
|
||||||
|
|
||||||
ts method dlnaloc {{urlbase ""}} {
|
ts method dlnaloc {{urlbase ""}} {
|
||||||
return [system dlnaurl [file normalize $file] $urlbase]
|
return [system dlnaurl $file $urlbase]
|
||||||
}
|
}
|
||||||
|
|
||||||
ts method cleanbmp {} {
|
ts method cleanbmp {} {
|
||||||
|
|
Loading…
Reference in New Issue