]> git.ipfire.org Git - thirdparty/tvheadend.git/commitdiff
transcoding: added initial UI for watching webm/hls streams in browser.
authorJohn Törblom <john.tornblom@gmail.com>
Thu, 18 Jul 2013 12:19:36 +0000 (14:19 +0200)
committerJohn Törblom <john.tornblom@gmail.com>
Thu, 18 Jul 2013 12:19:36 +0000 (14:19 +0200)
Navigation is very primitive. clicking on the logo will step up in
channel list, or you can use the up/down arrows on the keyboard. No UI
for listing channels is available, as this is more of a test to see
what browser are working.

src/webui/static/tv.css [new file with mode: 0644]
src/webui/static/tv.html [new file with mode: 0644]
src/webui/static/tv.js [new file with mode: 0644]

diff --git a/src/webui/static/tv.css b/src/webui/static/tv.css
new file mode 100644 (file)
index 0000000..b6b935f
--- /dev/null
@@ -0,0 +1,43 @@
+body {
+    background: black;
+    font-size: 150%;
+}
+
+#tv_videoPlayer {
+    z-index: 0;
+    width:100%; 
+    margin-right:auto; 
+    margin-left:auto;
+    display:block;
+}
+
+#tv_channelList {
+    z-index: 1;
+}
+
+#tv_statusBar {
+    position: fixed;
+    bottom: 5%;
+    border-radius: 10px;
+    background: rgba(128, 128, 128, 0.7);
+    width: 80%;
+    left: 10%;
+    z-index: 1;
+}
+
+#tv_statusBar table {
+    padding: 5px;
+    white-space: nowrap;
+}
+
+#sb_channelLogo {
+    width: 128px;
+    height: 128px;
+    float: left;
+    margin: 5px;
+    cursor: pointer;
+}
+
+#sb_channelName, #sb_nowTitle, #sb_nextTitle {
+    width: 100%;
+}
\ No newline at end of file
diff --git a/src/webui/static/tv.html b/src/webui/static/tv.html
new file mode 100644 (file)
index 0000000..f8763eb
--- /dev/null
@@ -0,0 +1,34 @@
+<!DOCTYPE html>
+<html>
+  <head>
+    <title>Tvheadend</title>
+    <script type="text/javascript" src="extjs/adapter/ext/ext-base.js"></script>
+    <script type="text/javascript" src="extjs/ext-all.js"></script>
+    <script type="text/javascript" src="tv.js"></script>
+    <script type="text/javascript">
+      Ext.onReady(tv.app.init, tv.app);
+    </script>
+
+    <link rel="stylesheet" type="text/css" href="tv.css">
+  </head>
+  <body>
+    <div id="tv_statusBar">
+      <img id="sb_channelLogo" src="htslogo.png">
+      <table>
+       <tr>
+         <td id="sb_channelName"></td>
+         <td id="sb_currentTime"></td>
+       </tr>
+       <tr>
+         <td id="sb_nowTitle"></td>
+         <td id="sb_nowDuration"></td>
+       </tr>
+       <tr>
+         <td id="sb_nextTitle"></td>
+         <td id="sb_nextDuration"></td>
+       </tr>
+      </table>
+    </div>
+    <video id="tv_videoPlayer"></video>
+  </body>
+</html>
diff --git a/src/webui/static/tv.js b/src/webui/static/tv.js
new file mode 100644 (file)
index 0000000..6d9b2a6
--- /dev/null
@@ -0,0 +1,321 @@
+
+if (VK_LEFT === undefined)
+    var VK_LEFT = 0x25;
+
+if (VK_UP === undefined)
+    var VK_UP = 0x26;
+
+if (VK_RIGHT === undefined)
+    var VK_RIGHT = 0x27;
+
+if (VK_DOWN === undefined)
+    var VK_DOWN = 0x28;
+
+if (VK_ENTER === undefined)
+    var VK_ENTER = 0x0d;
+
+if (VK_PLAY === undefined)
+    var VK_PLAY = 0x50;
+
+if (VK_PAUSE === undefined)
+    var VK_PAUSE = 0x51;
+
+if (VK_STOP === undefined)
+    var VK_STOP = 0x53;
+
+if (VK_BACK === undefined)
+    var VK_BACK = 0xa6;
+
+Ext.namespace('tv');
+
+tv.baseUrl = '../';
+
+tv.channelTags = new Ext.data.JsonStore({
+    autoLoad : true,
+    root : 'entries',
+    fields : [ 'identifier', 'name' ],
+    id : 'identifier',
+    sortInfo : {
+       field : 'name',
+       direction : "ASC"
+    },
+    url : tv.baseUrl + 'channeltags',
+    baseParams : {
+       op : 'listTags'
+    }
+});
+
+tv.channels = new Ext.data.JsonStore({
+    autoLoad : true,
+    root : 'entries',
+    fields : ['ch_icon', 'number', 'name', 'chid', 'tags'],
+    id : 'chid',
+    sortInfo : {
+       field : 'number',
+       direction : "ASC"
+    },
+    url : tv.baseUrl + "channels",
+    baseParams : {
+       op : 'list'
+    }
+});
+
+tv.epg = new Ext.data.JsonStore({
+    url : tv.baseUrl + "epg",
+    bufferSize : 300,
+    root : 'entries',
+    fields : [ 'id' ,
+              'channel',
+              'channelid',
+              'title',
+              'subtitle',
+              'episode',
+              'description',
+              'chicon',
+              {
+                  name : 'start',
+                  type : 'date',
+                  dateFormat : 'U' // unix time
+              }, 
+              {
+                  name : 'end',
+                  type : 'date',
+                  dateFormat : 'U' // unix time
+               }, 
+              'duration',
+              'contenttype',
+              'schedstate',
+              'serieslink',
+            ],
+    baseParams : {
+       limit : 2
+    }
+});
+
+
+tv.ui = function() {
+    //private space
+    // public space
+    return {
+    };
+
+}(); // end of ui
+
+
+
+tv.playback = function() {
+
+    //private space
+    var profiles = [
+       {
+           name:  'pass',
+           muxer: 'pass',
+           audio: 'UNKNOWN',
+           video: 'UNKNOWN',
+           subs:  'UNKNOWN',
+           canPlay: 'video/MP2T'
+       },
+       {
+           name:  'webm',
+           muxer: 'webm',
+           audio: 'VORBIS',
+           video: 'VP8',
+           subs:  'NONE',
+           canPlay: 'video/webm; codecs="vp8.0, vorbis"'
+       },
+       {
+           name:  'hls',
+           muxer: 'mpegts',
+           audio: 'AAC',
+           video: 'H264',
+           subs:  'NONE',
+           canPlay: 'application/vnd.apple.mpegURL; codecs="avc1.42E01E, mp4a.40.2'
+       },
+       {
+           name:  'hls',
+           muxer: 'mpegts',
+           audio: 'AAC',
+           video: 'H264',
+           subs:  'NONE',
+           canPlay: 'application/x-mpegURL; codecs="avc1.42E01E, mp4a.40.2'
+       },
+       {
+           name:  'ts',
+           muxer: 'mpegts',
+           audio: 'AAC',
+           video: 'H264',
+           subs:  'NONE',
+           canPlay: 'video/MP2T; codecs="avc1.42E01E, mp4a.40.2'
+       },
+       {
+           name:  'mkv',
+           muxer: 'matroska',
+           audio: 'AAC',
+           video: 'H264',
+           subs:  'NONE',
+           canPlay: 'video/x-matroska; codecs="avc1.42E01E, mp4a.40.2'
+       }
+    ];
+
+    // public space
+    return {
+       getProfile: function() {
+           var vid = document.createElement('video');
+           for(var i=0; i<profiles.length; i++)
+               if(vid.canPlayType(profiles[i].canPlay) == 'probably')
+                   return profiles[i];
+
+           for(var i=0; i<profiles.length; i++)
+               if(vid.canPlayType(profiles[i].canPlay) == 'maybe')
+                   return profiles[i];
+
+           return null;
+       },
+
+       getUrl: function(chid) {
+           var profile = this.getProfile();
+           var url = tv.baseUrl;
+
+           if(!profile) {
+               alert('Unsupported browser');
+               return '';
+           }
+
+           if(profile.name == 'hls')
+               url += 'playlist/channelid/'
+           else
+               url += 'stream/channelid/'
+
+           console.dir(profile);
+
+           url += chid;
+           url += "?transcode=1";
+           url += "&mux=" + profile.muxer;
+           url += "&acodec=" + profile.audio;
+           url += "&vcodec=" + profile.video;
+           url += "&scodec=" + profile.subs;
+           url += "&resolution=384";
+
+           return url;
+       }
+    };
+
+}(); // end of player
+
+tv.app = function() {
+    //private space
+
+    var updateClock = new Ext.util.DelayedTask(function() {
+       var clock = Ext.get('sb_currentTime');
+       clock.update(new Date().toLocaleTimeString());
+       updateClock.delay(1000);
+    });
+
+    var currentChannel = -1;
+    var initKeyboard = function() {
+
+       var channelLogoEl = document.getElementById('sb_channelLogo');
+       channelLogoEl.onclick = function() {
+           currentChannel += 1;
+           ch = tv.channels.getAt(currentChannel);
+           onChannelZap(ch.data);
+       }
+
+       document.onkeyup = function(e) {
+           switch(e.keyCode) {
+
+           case VK_UP:
+               var max = tv.channels.getTotalCount() - 1;
+               if(max < 0)
+                   return;
+
+               else if(currentChannel < max)
+                   currentChannel += 1;
+
+               else
+                   currentChannel = max;
+               
+               ch = tv.channels.getAt(currentChannel);
+               onChannelZap(ch.data);
+               break;
+
+           case VK_DOWN:
+               if(tv.channels.getTotalCount() == 0)
+                   return;
+
+               else if(currentChannel < 1)
+                   currentChannel = 0;
+
+               else
+                   currentChannel -= 1;
+
+               ch = tv.channels.getAt(currentChannel);
+               onChannelZap(ch.data);
+               break;
+           }
+       };
+    };
+
+    var onChannelZap = function(ch) {
+       var channelNameEl = document.getElementById('sb_channelName');
+       channelNameEl.innerHTML = ch.name;
+
+       var channelLogoEl = document.getElementById('sb_channelLogo');
+       if(ch.ch_icon) {
+           channelLogoEl.src = ch.ch_icon;
+           channelLogoEl.style = '';
+       } else {
+           channelLogoEl.src = '';
+           channelLogoEl.style = 'display: none';
+       }
+       
+       tv.epg.setBaseParam('channel', new String(ch.name));
+       tv.epg.load();
+
+       var videoPlayer = document.getElementById('tv_videoPlayer');
+       
+       setTimeout(function() {
+           videoPlayer.src = tv.playback.getUrl(ch.chid);
+           videoPlayer.load();
+           videoPlayer.play();
+       }, 1);
+    }
+
+    var initDataStores = function() {
+       tv.epg.on('load', function() {
+           var nowTitle = document.getElementById('sb_nowTitle');
+           var nowDuration = document.getElementById('sb_nowDuration');
+           var nextTitle = document.getElementById('sb_nextTitle');
+       var nextDuration = document.getElementById('sb_nextDuration');
+           var event = undefined;
+           
+           if(tv.epg.getTotalCount() < 1) {
+               nowTitle.innerHTML = '';
+               nowDuration.innerHTML = '';
+           } else {
+               event = tv.epg.getAt(0).data;
+               nowTitle.innerHTML = event.title;
+           nowDuration.innerHTML = event.start.toLocaleTimeString();
+           }
+           
+           if(tv.epg.getTotalCount() < 2) {
+               nextTitle.innerHTML = '';
+               nextDuration.innerHTML = '';
+           } else {
+               event = tv.epg.getAt(1).data;
+               nextTitle.innerHTML = event.title;
+               nextDuration.innerHTML = event.start.toLocaleTimeString();
+           }
+       });
+    };
+
+    // public space
+    return {
+       init: function() {
+           initDataStores();
+           initKeyboard();
+           updateClock.delay(1);
+       }
+    };
+}(); // end of app