diff -Naur mythweb/includes/utils.php mythweb/includes/utils.php --- mythweb/includes/utils.php 2018-01-11 14:34:13.000000000 +0100 +++ mythweb/includes/utils.php 2018-01-11 14:37:45.233333332 +0100 @@ -246,6 +246,8 @@ case 'flvp': return "$url.flvp"; case 'flv' : return "$url.flv"; case 'mp4' : return "$url.mp4"; + case 'ogv' : return "$url.ogv"; + case 'webm': return "$url.webm"; } // No more dsmyth filters, so return the URL no matter what the browser is. return $url; diff -Naur mythweb/modules/mythweb/set_flvplayer.php mythweb/modules/mythweb/set_flvplayer.php --- mythweb/modules/mythweb/set_flvplayer.php 2018-01-11 14:34:13.000000000 +0100 +++ mythweb/modules/mythweb/set_flvplayer.php 2018-01-11 14:37:45.233333332 +0100 @@ -23,6 +23,8 @@ // Bitrates setting('WebFLV_vb', null, $_POST['vbitrate'] > 0 ? $_POST['vbitrate'] : 256); setting('WebFLV_ab', null, $_POST['abitrate'] > 0 ? $_POST['abitrate'] : 64); + // HTML5 streaming on/off + setting('WebHTML5Stream_on', null, $_POST['HTML5stream'] ? 1 : 0); } diff -Naur mythweb/modules/mythweb/tmpl/default/set_flvplayer.php mythweb/modules/mythweb/tmpl/default/set_flvplayer.php --- mythweb/modules/mythweb/tmpl/default/set_flvplayer.php 2018-01-11 14:34:13.000000000 +0100 +++ mythweb/modules/mythweb/tmpl/default/set_flvplayer.php 2018-01-11 14:37:45.233333332 +0100 @@ -24,12 +24,12 @@ " /> kbps
- Flash video playback is currently only a proof-of-concept and should be - considered EXTREMELY experimental, which is why it has been disabled by - default. -
+ Video streaming in mythweb is currently based on HTML5 webm/ogg and flash flv. + NOTE: Today there is no single format supported by all browsers. + By this different browsers needs different configuration- It currently expects that ffmpeg is installed and compiled with mp3 - support, and that the recordings files are accessible to your webserver - userid. It probably won't work with Nupplevideo files, and in the end, - it may just not work at all (or maybe even worse). + This feature expects that myth is build with: libx264, libvpx and libtheora libraries. Also for + flash fallback, Adobe Flash player must be installed on client side.
- Enable this feature at your own risk, and don't expect too much - official help until it has left the experimental phase. + Enabling HTML5 streaming may cause not stable streaming as majority browsers (except Mozzilla) + offers not good video playabck stability. By this prefered mode is Flash fall-back mode + (with "HTML5 Streaming" option disabled).
"info: hidden advanced schedule" The advanced scheduling options are currently hidden. @@ -358,8 +369,8 @@ "Internet Reference #" "Jump" "Jump Points" -"Jump to" "Jump To" +"Jump to" "JumpPoints Editor" "Key Bindings" "Key bindings" @@ -377,8 +388,8 @@ "Left" "left" "leftl" -"length" "Length" +"length" "Length (min)" "Length in minutes" "Listing "Jump to"" @@ -414,8 +425,8 @@ "Modify unidentified episodes" "Monday" "Mono" -"more" "More" +"more" "Move Item Down in Playlist" "Move Item Up in Playlist" "movie" @@ -423,6 +434,7 @@ "Movies" "Movies, 3½ Stars or more" "Movies, Stinkers (2 Stars or less)" +"mplexid" "Music" "Music Specials" "My Session" @@ -515,6 +528,7 @@ "Play" "Play Recording on Frontend" "Play this Album Now" +"Play this Artist Now" "Play This Playlist Now" "Play this Playlist Now" "Play this Song Now" @@ -563,8 +577,8 @@ "Priority for movies by the year of release" "Priority when shown once" "Produced by" -"profile" "Profile" +"profile" "Program Categories" "Program Detail" "Program ID" @@ -582,8 +596,8 @@ "Rating:" "Recently Added Albums" "Recently completed jobs" -"Recently Played Songs" "Recently Played Albums" +"Recently Played Songs" "recgroup" "Recommend Videos" "Recommended" @@ -688,8 +702,8 @@ "Reset template and skin to defaults" "Retry" "Return to Statistics Page" -"right" "Right" +"right" "Root Directory" "Rows to show between timeslot info" "Running" @@ -732,11 +746,10 @@ "Select the correct show" "Server returned invalid data when attempting to retrieve metadata." "Server Statistics" -"mplexid" "serviceid" "Set Host" -"settings" "Settings" +"settings" "Settings Table" "settings/stream: protocol" Many media players are incapable of playing files that are streamed from
+ Video streaming in mythweb is currently based on HTML5 webm/ogg and flash flv.
+ NOTE: Today there is no single format supported by all browsers..
+ By this different browsers needs different configuration
+ mythweb provides following streaming support:
+
+ This feature expects that myth is build with libx264, libvpx and libtheora libraries. Also for + flash fallback, Adobe Flash player must be installed on client side. +
++ Enabling HTML5 streaming may cause not stable streaming as majority browsers (except Mozzilla). + offers not good video playabck stability. By this prefered mode is Flash fall-back mode. + (with "HTML5 Streaming" option disabled). +
"info: hidden advanced schedule" "info: record this" "info:forget old" @@ -403,10 +430,10 @@ "Jump" Skocz "Jump Points" -"Jump to" - Skocz do "Jump To" Skocz Do +"Jump to" + Skocz do "JumpPoints Editor" "Key Bindings" "Key bindings" @@ -423,13 +450,13 @@ "Last recording" "Last showing of each episode" "Later" -"left" "Left" +"left" "leftl" -"length" - długość "Length" Długość +"length" + długość "Length (min)" Czas trwania (minut): "Length in minutes" @@ -463,7 +490,6 @@ "Missing Cover" "Modify priority by star rating (0.0 to 1.0 for movies only)" "Modify priority for a station on an input" -"Modify priority for all inputs on a card" "Modify priority for an input (Input priority)" "Modify priority for every card on a host" "Modify unidentified episodes" @@ -481,6 +507,7 @@ "Movies, 3½ Stars or more" Filmy 3½ gwiazdy lub więcej "Movies, Stinkers (2 Stars or less)" +"mplexid" "Music" Muzyka "Music Specials" @@ -520,6 +547,7 @@ Nowe tytuły, premiery "No" "NO DATA" +"No episodes" "No Frontends allow remote control." "No Genre" "No matches found" @@ -579,6 +607,7 @@ "Play" "Play Recording on Frontend" "Play this Album Now" +"Play this Artist Now" "Play This Playlist Now" "Play this Playlist Now" "Play this Song Now" @@ -649,6 +678,7 @@ "Rating:" "Recently Added Albums" "Recently completed jobs" +"Recently Played Albums" "Recently Played Songs" "recgroup" "Recommend Videos" @@ -764,8 +794,8 @@ "Reset template and skin to defaults" "Retry" "Return to Statistics Page" -"right" "Right" +"right" "Root Directory" "Rows to show between timeslot info" "Running" @@ -820,7 +850,6 @@ "Server returned invalid data when attempting to retrieve metadata." "Server Statistics" "serviceid" -"mplexid" "Set Host" "Settings" Ustawienia @@ -909,20 +938,22 @@ "Time Span" "Time Stretch" "Timeslot size" -"title" - tytuł "Title" Tytuł +"title" + tytuł "Title Match" -"Title search" "Title Search" Szukanie Tytułu +"Title search" "Title Search:" "Title:" "Toggle Interactive Mode" "Too Many" "Top $1" Szczyt $1 +"Top Played Albums" +"Top Played Artist" "Top Played Songs" "Top Rated Songs" "Total Length" @@ -932,20 +963,21 @@ Całkowity Czas "Total Time: %s" "Track #%s from the album '%s'" +"Trakt.tv Home Page" "Transcode" "Transcoded" -"transcoder" "Transcoder" +"transcoder" "Tuesday" Wtorek "Tuner Busy" "TV" "TV functions, including recorded programs." "TV.com" -"type" - typ "Type" Typ +"type" + typ "Uncategorized" "Undelete" "Undelete: $1" diff -Naur mythweb/modules/stream/handler.pl mythweb/modules/stream/handler.pl --- mythweb/modules/stream/handler.pl 2018-01-11 14:34:13.000000000 +0100 +++ mythweb/modules/stream/handler.pl 2018-01-11 14:37:45.236666666 +0100 @@ -12,14 +12,35 @@ require "modules/$Path[0]/tv.pl"; +# Use the MythTV Services API URL if the $filename URL is not local unless ($filename) { - print header(), - "$basename does not exist in any recognized storage group directories for this host."; - exit; + # Retrieve the backend IP and port + $sh = $dbh->prepare('SELECT data FROM settings WHERE value=?'); + $sh->execute('BackendServerIP'); + my ($backend_server_ip) = $sh->fetchrow_array; + $sh->execute('BackendStatusPort'); + my ($backend_status_port) = $sh->fetchrow_array; + $sh->finish(); + + # Reformat the recording start time + use HTTP::Date qw(time2isoz); + $starttime_isoz = time2isoz($starttime); + $starttime_isoz =~ s/ /T/g; + + # Generate the MythTV Services API URL + $filename = "http://${backend_server_ip}:${backend_status_port}/Content/GetRecording?ChanId=${chanid}&StartTime=${starttime_isoz}"; } +# HTML5 video/ogv + if ($ENV{'REQUEST_URI'} =~ /\.ogv$/i) { + require "modules/$Path[0]/stream_ogv.pl"; + } +# HTML5 video/webm + elsif ($ENV{'REQUEST_URI'} =~ /\.webm$/i) { + require "modules/$Path[0]/stream_webm.pl"; + } # ASX mode? - if ($ENV{'REQUEST_URI'} =~ /\.asx$/i) { + elsif ($ENV{'REQUEST_URI'} =~ /\.asx$/i) { require "modules/$Path[0]/stream_asx.pl"; } # Flash? diff -Naur mythweb/modules/stream/stream_ogv.pl mythweb/modules/stream/stream_ogv.pl --- mythweb/modules/stream/stream_ogv.pl 1970-01-01 01:00:00.000000000 +0100 +++ mythweb/modules/stream/stream_ogv.pl 2018-01-11 13:31:00.000000000 +0100 @@ -0,0 +1,190 @@ +#!/usr/bin/perl +# +# MythWeb Streaming/Download module +# +# @url $URL$ +# @date $2016/11/23$ +# @version $v1.0$ +# @author $Piotr Oniszczuk$ +# + + use POSIX qw(ceil floor); + +# round to the nearest even integer + sub round_even { + my ($in) = @_; + my $n = floor($in); + return ($n % 2 == 0) ? $n : ceil($in); + } + + our $ffmpeg_pid; + our $ffmpeg_pgid; + +# Shutdown cleanup + $ffmpeg_pgid = setpgrp(0,0); + $SIG{'TERM'} = \&shutdown_handler; + $SIG{'PIPE'} = \&shutdown_handler; + END { + shutdown_handler(); + } + sub shutdown_handler { + kill(1, $ffmpeg_pid) if ($ffmpeg_pid); + kill(-1, $ffmpeg_pid) if ($ffmpeg_pid); + } + +# Find ffmpeg + $ffmpeg = ''; + foreach my $path (split(/:/, $ENV{'PATH'}.':/usr/local/bin:/usr/bin'), '.') { + if (-e "$path/mythffmpeg") { + $ffmpeg = "$path/mythffmpeg"; + last; + } + if (-e "$path/ffmpeg") { + $ffmpeg = "$path/ffmpeg"; + last; + } + elsif ($^O eq 'darwin' && -e "$path/ffmpeg.app") { + $ffmpeg = "$path/ffmpeg.app"; + last; + } + } + +# Load some conversion settings from the database + $sh = $dbh->prepare('SELECT data FROM settings WHERE value=? AND hostname IS NULL'); + $sh->execute('WebFLV_w'); + my ($width) = $sh->fetchrow_array; + $sh->execute('WebFLV_vb'); + my ($vbitrate) = $sh->fetchrow_array; + $sh->execute('WebFLV_ab'); + my ($abitrate) = $sh->fetchrow_array; + $sh->finish(); + +# auto-detect height based on aspect ratio + $sh = $dbh->prepare('SELECT data FROM recordedmarkup WHERE chanid=? ' . + 'AND starttime=FROM_UNIXTIME(?) AND type=30 ' . + 'AND data IS NOT NULL ORDER BY mark LIMIT 1'); + $sh->execute($chanid,$starttime); + $x = $sh->fetchrow_array; # type = 30 + $sh->finish(); + + $sh = $dbh->prepare('SELECT data FROM recordedmarkup WHERE chanid=? ' . + 'AND starttime=FROM_UNIXTIME(?) AND type=31 ' . + 'AND data IS NOT NULL ORDER BY mark LIMIT 1'); + $sh->execute($chanid,$starttime); + $y = $sh->fetchrow_array if ($x); # type = 31 + $sh->finish(); + + if (!$x || !$y || $x <= 720) { # <=720 means SD + $sh = $dbh->prepare('SELECT recordedmarkup.type, ' . + 'recordedmarkup.data '. + 'FROM recordedmarkup ' . + 'WHERE recordedmarkup.chanid = ? ' . + 'AND recordedmarkup.starttime = FROM_UNIXTIME(?) ' . + 'AND recordedmarkup.type IN (10, 11, 12, 13, 14) ' . + 'GROUP BY recordedmarkup.type ' . + 'ORDER BY SUM((SELECT IFNULL(rm.mark, recordedmarkup.mark) ' . + ' FROM recordedmarkup AS rm ' . + ' WHERE rm.chanid = recordedmarkup.chanid ' . + ' AND rm.starttime = recordedmarkup.starttime ' . + ' AND rm.type IN (10, 11, 12, 13, 14) ' . + ' AND rm.mark > recordedmarkup.mark ' . + ' ORDER BY rm.mark ASC LIMIT 1)- recordedmarkup.mark) DESC ' . + 'LIMIT 1'); + $sh->execute($chanid,$starttime); + $aspect = $sh->fetchrow_hashref; + $sh->finish(); + + if( $aspect->{'type'} ) { + if( $aspect->{'type'} == 10 ) { + $x = $y = 1; + } elsif( $aspect->{'type'}== 11 ) { + $x = 4; $y = 3; + } elsif( $aspect->{'type'}== 12 ) { + $x = 16; $y = 9; + } elsif( $aspect->{'type'}== 13 ) { + $x = 2.21; $y = 1; + } elsif( $aspect->{'type'}== 14 ) { + $x = $aspect->{'data'}; $y = 1000000; + } else { + $x = 4; $y = 3; + } + } + else { + $x = 4; $y = 3; + } + + } + $height = round_even($width * ($y/$x)); + + $width = 320 unless ($width && $width > 1); + $height = 240 unless ($height && $height > 1); + $vbitrate = 256 unless ($vbitrate && $vbitrate > 1); + $abitrate = 64 unless ($abitrate && $abitrate > 1); + +# build appropriate encoder commad + my $ffmpeg_command = $ffmpeg + .' -y' + .' -i '.shell_escape("$filename") + .' -s '.shell_escape("${width}x${height}") + .' -g 30' + .' -r 24' + .' -f ogg' + .' -codec:a vorbis' + .' -codec:v libtheora' + .' -strict -2' + .' -deinterlace' + .' -async 2' + .' -ac 2' + .' -ar 44100' + .' -b:a '.shell_escape("${abitrate}k") + .' -b:v '.shell_escape("${vbitrate}k") + .' -sn' + .' /dev/stdout 2>/dev/null |'; + +# start to encode content + $ffmpeg_pid = open(DATA, $ffmpeg_command); + unless ($ffmpeg_pid) { + print header(), + "Can't execute ffmpeg. Command was: $!\n${ffmpeg_command}"; + exit; + } + +# Guess the filesize based on duration and bitrate. This allows progressive download. Print header with +# guessed file size + my $lengthSec; + my $length; + my $range; + $dur = `$ffmpeg -i $filename 2>&1 | /usr/bin/grep "Duration" | /usr/bin/cut -d ' ' -f 4 | /bin/sed s/,//`; + if ($dur && $dur =~ /\d*:\d*:.*/) { + @times = split(':',$dur); + $lengthSec = $times[0]*3600+$times[1]*60+$times[2]; + $length = int($lengthSec); + $size = int(0.85*$lengthSec*($vbitrate*1024+$abitrate*1024)/8); + $range = $size-1; + print header(-type => 'video/ogg', + -Content_length => $size, + -Accept_Ranges => 'bytes', + -Timing_Allow_Origin => '*', + -Content_Duration => $length, + -Content_Range => 'bytes 0-'.$range.'/'.$size, + -X_Content_Duration => $length + ); + } else { + print header(-type => 'video/ogg'); + + } + + if ($ENV{'REQUEST_METHOD'} eq 'HEAD') { + exit; + } + +# send encoded data to browser + my $buffer; + while (read DATA, $buffer, 262144) { + unless (print $buffer ) { + last; + } + } + + close DATA; + diff -Naur mythweb/modules/stream/stream_webm.pl mythweb/modules/stream/stream_webm.pl --- mythweb/modules/stream/stream_webm.pl 1970-01-01 01:00:00.000000000 +0100 +++ mythweb/modules/stream/stream_webm.pl 2018-01-11 13:28:00.000000000 +0100 @@ -0,0 +1,191 @@ +#!/usr/bin/perl +# +# MythWeb Streaming/Download module +# +# @url $URL$ +# @date $2016/11/23$ +# @version $v1.0$ +# @author $Piotr Oniszczuk$ +# + + use POSIX qw(ceil floor); + +# round to the nearest even integer + sub round_even { + my ($in) = @_; + my $n = floor($in); + return ($n % 2 == 0) ? $n : ceil($in); + } + + our $ffmpeg_pid; + our $ffmpeg_pgid; + +# Shutdown cleanup + $ffmpeg_pgid = setpgrp(0,0); + $SIG{'TERM'} = \&shutdown_handler; + $SIG{'PIPE'} = \&shutdown_handler; + END { + shutdown_handler(); + } + sub shutdown_handler { + kill(1, $ffmpeg_pid) if ($ffmpeg_pid); + kill(-1, $ffmpeg_pid) if ($ffmpeg_pid); + } + +# Find ffmpeg + $ffmpeg = ''; + foreach my $path (split(/:/, $ENV{'PATH'}.':/usr/local/bin:/usr/bin'), '.') { + if (-e "$path/mythffmpeg") { + $ffmpeg = "$path/mythffmpeg"; + last; + } + if (-e "$path/ffmpeg") { + $ffmpeg = "$path/ffmpeg"; + last; + } + elsif ($^O eq 'darwin' && -e "$path/ffmpeg.app") { + $ffmpeg = "$path/ffmpeg.app"; + last; + } + } + +# Load some conversion settings from the database + $sh = $dbh->prepare('SELECT data FROM settings WHERE value=? AND hostname IS NULL'); + $sh->execute('WebFLV_w'); + my ($width) = $sh->fetchrow_array; + $sh->execute('WebFLV_vb'); + my ($vbitrate) = $sh->fetchrow_array; + $sh->execute('WebFLV_ab'); + my ($abitrate) = $sh->fetchrow_array; + $sh->finish(); + +# auto-detect height based on aspect ratio + $sh = $dbh->prepare('SELECT data FROM recordedmarkup WHERE chanid=? ' . + 'AND starttime=FROM_UNIXTIME(?) AND type=30 ' . + 'AND data IS NOT NULL ORDER BY mark LIMIT 1'); + $sh->execute($chanid,$starttime); + $x = $sh->fetchrow_array; # type = 30 + $sh->finish(); + + $sh = $dbh->prepare('SELECT data FROM recordedmarkup WHERE chanid=? ' . + 'AND starttime=FROM_UNIXTIME(?) AND type=31 ' . + 'AND data IS NOT NULL ORDER BY mark LIMIT 1'); + $sh->execute($chanid,$starttime); + $y = $sh->fetchrow_array if ($x); # type = 31 + $sh->finish(); + + if (!$x || !$y || $x <= 720) { # <=720 means SD + $sh = $dbh->prepare('SELECT recordedmarkup.type, ' . + 'recordedmarkup.data '. + 'FROM recordedmarkup ' . + 'WHERE recordedmarkup.chanid = ? ' . + 'AND recordedmarkup.starttime = FROM_UNIXTIME(?) ' . + 'AND recordedmarkup.type IN (10, 11, 12, 13, 14) ' . + 'GROUP BY recordedmarkup.type ' . + 'ORDER BY SUM((SELECT IFNULL(rm.mark, recordedmarkup.mark) ' . + ' FROM recordedmarkup AS rm ' . + ' WHERE rm.chanid = recordedmarkup.chanid ' . + ' AND rm.starttime = recordedmarkup.starttime ' . + ' AND rm.type IN (10, 11, 12, 13, 14) ' . + ' AND rm.mark > recordedmarkup.mark ' . + ' ORDER BY rm.mark ASC LIMIT 1)- recordedmarkup.mark) DESC ' . + 'LIMIT 1'); + $sh->execute($chanid,$starttime); + $aspect = $sh->fetchrow_hashref; + $sh->finish(); + + if( $aspect->{'type'} ) { + if( $aspect->{'type'} == 10 ) { + $x = $y = 1; + } elsif( $aspect->{'type'}== 11 ) { + $x = 4; $y = 3; + } elsif( $aspect->{'type'}== 12 ) { + $x = 16; $y = 9; + } elsif( $aspect->{'type'}== 13 ) { + $x = 2.21; $y = 1; + } elsif( $aspect->{'type'}== 14 ) { + $x = $aspect->{'data'}; $y = 1000000; + } else { + $x = 4; $y = 3; + } + } + else { + $x = 4; $y = 3; + } + } + $height = round_even($width * ($y/$x)); + + $width = 320 unless ($width && $width > 1); + $height = 240 unless ($height && $height > 1); + $vbitrate = 256 unless ($vbitrate && $vbitrate > 1); + $abitrate = 64 unless ($abitrate && $abitrate > 1); + +# build appropriate encoder commad + my $ffmpeg_command = $ffmpeg + .' -y' + .' -i '.shell_escape("$filename") + .' -s '.shell_escape("${width}x${height}") + .' -g 30' + .' -r 24' + .' -f webm' + .' -codec:a vorbis' + .' -codec:v libvpx' + .' -cpu-used -8' + .' -deadline realtime' + .' -strict -2' + .' -deinterlace' + .' -async 2' + .' -ac 2' + .' -ar 44100' + .' -b:a '.shell_escape("${abitrate}k") + .' -b:v '.shell_escape("${vbitrate}k") + .' -sn' + .' /dev/stdout 2>/dev/null |'; + +# start to encode content + $ffmpeg_pid = open(DATA, $ffmpeg_command); + unless ($ffmpeg_pid) { + print header(), + "Can't execute ffmpeg. Command was: $!\n${ffmpeg_command}"; + exit; + } + +# Guess the filesize based on duration and bitrate. This allows progressive download. Print header with +# guessed file size + my $lengthSec; + my $length; + my $range; + $dur = `$ffmpeg -i $filename 2>&1 | /usr/bin/grep "Duration" | /usr/bin/cut -d ' ' -f 4 | /bin/sed s/,//`; + if ($dur && $dur =~ /\d*:\d*:.*/) { + @times = split(':',$dur); + $lengthSec = $times[0]*3600+$times[1]*60+$times[2]; + $length = int($lengthSec); + $size = int(0.90*$lengthSec*($vbitrate*1024+$abitrate*1024)/8); + $range = $size-1; + print header(-type => 'video/webm', + -Content_length => $size, + -Accept_Ranges => 'bytes', + -Timing_Allow_Origin => '*', + -Content_Duration => $length, + -Content_Range => 'bytes 0-'.$range.'/'.$size, + -X_Content_Duration => $length + ); + } else { + print header(-type => 'video/webm'); + + } + + if ($ENV{'REQUEST_METHOD'} eq 'HEAD') { + exit; + } + +# send encoded data to browser + my $buffer; + while (read DATA, $buffer, 262144) { + unless (print $buffer ) { + last; + } + } + + close DATA; + diff -Naur mythweb/modules/tv/tmpl/default/detail.php mythweb/modules/tv/tmpl/default/detail.php --- mythweb/modules/tv/tmpl/default/detail.php 2018-01-11 14:34:14.000000000 +0100 +++ mythweb/modules/tv/tmpl/default/detail.php 2018-01-11 14:37:45.236666666 +0100 @@ -37,6 +37,12 @@ +