diff --git a/src/webui/static/tv.css b/src/webui/static/tv.css
new file mode 100644
index 00000000..b6b935ff
--- /dev/null
+++ b/src/webui/static/tv.css
@@ -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
index 00000000..f8763eb4
--- /dev/null
+++ b/src/webui/static/tv.html
@@ -0,0 +1,34 @@
+
+
+
+ Tvheadend
+
+
+
+
+
+
+
+
+
+

+
+
+ |
+ |
+
+
+ |
+ |
+
+
+ |
+ |
+
+
+
+
+
+
diff --git a/src/webui/static/tv.js b/src/webui/static/tv.js
new file mode 100644
index 00000000..6d9b2a62
--- /dev/null
+++ b/src/webui/static/tv.js
@@ -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