]> git.mdlowis.com Git - proto/atv.git/commitdiff
simplified main control page. fixed some bugs. added admin control page
authorMichael D. Lowis <mike@mdlowis.com>
Tue, 5 Mar 2024 04:45:11 +0000 (23:45 -0500)
committerMichael D. Lowis <mike@mdlowis.com>
Tue, 5 Mar 2024 04:45:11 +0000 (23:45 -0500)
atv/assets/admin.html [new file with mode: 0644]
atv/assets/control.html
atv/assets/index.html
atv/bin/atv
atv/lib/atv/channel.rb
atv/lib/atv/database.rb

diff --git a/atv/assets/admin.html b/atv/assets/admin.html
new file mode 100644 (file)
index 0000000..719cbec
--- /dev/null
@@ -0,0 +1,326 @@
+<!DOCTYPE html>
+<html dir="ltr" lang="en-US">
+<head>
+  <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
+  <meta name="viewport" content="width=device-width,initial-scale=1">
+  <style>
+    html, body {
+      margin: 0;
+      padding: 0;
+      width: 100%;
+      height: 100%;
+      max-height: 100%;
+      background-color: #eaeaea;
+      overflow: hidden;
+    }
+
+    .table {
+        display: block;
+    }
+
+    .row {
+        display: flex;
+        flex: 1 0 auto;
+        border-bottom: 1px solid black;
+    }
+
+    .label {
+        flex-shrink: 1;
+        padding: 1em;
+        width: 8em;
+        border-right: 1px solid black;
+    }
+
+    .item {
+        flex-grow: 1;
+        border-left: 0;
+        padding: 1em;
+    }
+
+    .row input {
+        flex-grow: 1;
+        height: 4em;
+    }
+
+    td {
+        border: 1px solid #000;
+        padding: 1em;
+    }
+
+    tr td:first-child {
+        width: 1%;
+        white-space: nowrap;
+    }
+
+    input {
+        margin: 0.5em;
+    }
+
+    article {
+        display: flex;
+        flex-flow: column;
+        height: 100%;
+        max-height: 100%;
+    }
+
+    article section {
+        //border: 1px dotted black;
+        flex: 0 1 auto;
+    }
+
+    .grow {
+        flex: 1 1 auto;
+        overflow: auto;
+    }
+
+    .shrink {
+        flex: 0 1 auto;
+    }
+
+    #breadCrumbs {
+        margin-left: 1em;
+        margin-right: 1em;
+    }
+
+    #itemList {
+        margin-left: 1em;
+        margin-right: 1em;
+    }
+
+
+  </style>
+</head>
+<body>
+
+<article id="mainView" style="display: flex">
+    <section>
+        <table style="width: 100%"><tbody>
+            <tr>
+              <td><strong>Now Playing</strong></td>
+              <td class="item" id="currVid">Some File</td>
+            </tr>
+            <tr>
+              <td><strong>Playing Next</strong></td>
+              <td class="item" id="nextVid">Some Other File</td>
+            </tr>
+        </tbody></table>
+    </section>
+
+    <section class="grow">
+        <div style="width: 100%; text-align: center"><h2>Queued Videos</h2></div>
+        <div id="queueList" style="width: 100%; text-align: center;">The queue is currently empty</div>
+    </section>
+
+    <section>
+        <div class="row">
+            <input type="button" value="Play" onclick="javascript:UI.play_pause()" id="playBtn"/>
+            <input type="button" value="Chan +" onclick="javascript:UI.chan_next()"/>
+            <input type="button" value="Chan -" onclick="javascript:UI.chan_prev()"/>
+            <input type="button" value="Skip" onclick="javascript:UI.skip()"/>
+        </div>
+    </section>
+</article>
+
+<article id="browseView" style="display: none">
+    <section>
+        <h1 style="text-align: center">Browse Media</h1>
+        <hr/>
+        <div>
+          <input type="button" value="Up" onclick="javascript:UI.browse_up()"/>
+           <span id="currDir"></span>
+        </div>
+        <hr/>
+    </section>
+
+    <section class="grow">
+        <div id="itemList">contents here</div>
+    </section>
+
+    <section>
+        <hr/>
+        <div class="row"><input type="button" value="Cancel"  onclick="javascript:UI.cancel()"/></div>
+    </section>
+</article>
+
+<script type="text/javascript" src="client.js"></script>
+
+<script>
+const UI = (()=>{
+    const self = {};
+
+    let queue = [];
+    let items = {};
+    let path = [];
+
+    const showBrowser = ()=>{
+        path = [];
+        browseView.style.display = "flex";
+        mainView.style.display = "none";
+    };
+
+    const showMain = ()=>{
+        path = [];
+        browseView.style.display = "none";
+        mainView.style.display = "flex";
+    };
+
+    // Button handlers
+    self.play_pause  = ()=>{ Client.play_pause(); };
+    self.chan_next   = ()=>{ Client.chan_next(); };
+    self.chan_prev   = ()=>{ Client.chan_prev(); };
+    self.skip        = ()=>{ Client.skip(); };
+    self.select_file = ()=>{ showBrowser(); };
+    self.cancel      = ()=>{ showMain(); };
+
+    self.enqueue = (path)=>{
+        console.log(path);
+        Client.send("enqueue", { path: path });
+        showMain();
+    };
+
+    self.set_items = (new_items)=>{
+        items = new_items;
+        refreshItems();
+    };
+
+    self.set_queue = (new_queue)=>{
+        queue = new_queue;
+        refreshQueue();
+    };
+
+    self.set_play_state = (up_now, up_next, playing)=>{
+        currVid.innerText = up_now;
+        nextVid.innerText = up_next;
+        playBtn.value = (playing ? "Pause" : "Play");
+    };
+
+    self.browse_up = ()=>{
+        path.pop();
+        refreshItems();
+    };
+
+
+
+    const refreshQueue = ()=>{
+        if (queue.length > 0) {
+            queueList.innerHTML = "";
+            for (const item of queue) {
+                const el = document.createElement("div");
+                el.innerText = item.path;
+                queueList.appendChild(el);
+            }
+        } else {
+            queueList.innerHTML = "The queue is currently empty";
+        }
+    };
+
+    const getLocalRoot = ()=>{
+        let root = items;
+        for (const dir of path) {
+            root = root[dir];
+        }
+        return root;
+    };
+
+    const linkDown = (dir)=>{
+        path.push(dir);
+        refreshItems();
+    };
+
+    const refreshItems = ()=>{
+        const dir = getLocalRoot();
+        currDir.innerText = (path[path.length - 1] || "Root");
+
+        itemList.innerText = "";
+        for (const item in dir) {
+            const el = document.createElement("div");
+            const link = document.createElement("a");
+            link.innerText = item;
+            link.href = '#';
+            if ('path' in dir[item]) {
+                link.onclick = ()=>{ self.enqueue(dir[item].path); };
+            } else {
+                link.onclick = ()=>{ linkDown(item); };
+            }
+            el.appendChild(link);
+            itemList.appendChild(el);
+        }
+    };
+
+    return self;
+})();
+
+
+(()=>{
+    let items = {}
+    let path = [];
+
+    Client.connect((data)=>{
+        console.log(data);
+        if (cmd.cmd === "items")
+        {
+            UI.set_items( data.items );
+        }
+        else if (cmd.cmd === "queue")
+        {
+            UI.set_queue(data.queue);
+        }
+        else
+        {
+            UI.set_play_state(
+                data["curr"]["path"], // Now Playing
+                data["next"]["path"], // Up next
+                data.playing          // Playing or paused
+            );
+        }
+    });
+
+
+
+
+//    const getLocalRoot = ()=>{
+//        let root = items;
+//        for (const dir of path) {
+//            root = root[dir];
+//        }
+//        return root;
+//    };
+//
+//    const linkDown = (dir)=>{
+//        path.push(dir);
+//        showItems();
+//    };
+//
+//    const linkUp = ()=>{
+//        path.slice(-1);
+//        showItems();
+//    };
+//
+//    const showItem = (item, isFile, fileData) => {
+//        const el = document.createElement("div");
+//        const link = document.createElement("a");
+//        link.innerText = item;
+//        link.href = '#';
+//        if (!isFile) {
+//            link.onclick = ()=>{ linkDown(item); };
+//        } else {
+//            link.onclick = ()=>{ Client.enqueue(fileData.path); };
+//        }
+//        el.appendChild(link);
+//        itemList.appendChild(el);
+//    }
+//
+//    const showItems = ()=>{
+//        const dir = getLocalRoot();
+//        itemList.innerText = "";
+//        for (const item in dir) {
+//            showItem(item, ('path' in dir[item]), dir[item]);
+//        }
+//    };
+
+})();
+
+
+</script>
+</body>
+</html>
index c232fee24ea79f117096337cba5b8475a3cc2379..2e0f5e30477052b40940f0814b6ef62c7adcd9b0 100644 (file)
 
     <section>
         <div class="row">
-            <input type="button" value="Play" onclick="javascript:UI.play_pause()" id="playBtn"/>
-            <input type="button" value="Chan +" onclick="javascript:UI.chan_next()"/>
-            <input type="button" value="Chan -" onclick="javascript:UI.chan_prev()"/>
             <input type="button" value="Enqueue" onclick="javascript:UI.select_file()"/>
         </div>
     </section>
         <h1 style="text-align: center">Browse Media</h1>
         <hr/>
         <div>
-          <input type="button" value="Up" onclick="javascript:UI.browse_up()" id="playBtn"/>
+          <input type="button" value="Up" onclick="javascript:UI.browse_up()"/>
            <span id="currDir"></span>
         </div>
         <hr/>
@@ -190,7 +187,6 @@ const UI = (()=>{
     self.set_play_state = (up_now, up_next, playing)=>{
         currVid.innerText = up_now;
         nextVid.innerText = up_next;
-        playBtn.value = (playing ? "Pause" : "Play");
     };
 
     self.browse_up = ()=>{
index 398af0808e7d8ce1c2c224b423e8d5934e04910f..604eafff68a7e9b976cf2324bc4cfd83068648f9 100644 (file)
@@ -78,7 +78,7 @@ const updateChannelTitle = ()=>{
     }
 };
 
-const updatePlayer = ()=>{
+const updatePlayer = (prev)=>{
     const curr_time = secondsSinceMidnight();
     if (!current["start_time"])
     {
@@ -93,7 +93,10 @@ const updatePlayer = ()=>{
     const diffTooBig = Math.abs(Math.floor(Video.currentTime) - elapsed) > 3;
     const srcChanged = Video.src.replace(/#t=[0-9]+$/, '') != encodeURI(document.location + current["curr"]["path"]);
 
-    if (diffTooBig || srcChanged)
+    const playStateChanged = prev["playing"] != current["playing"];
+    const chanChanged = prev["chan"] != current["chan"];
+
+    if (diffTooBig || srcChanged || playStateChanged || chanChanged)
     {
         Video.src = current["curr"]["path"] + "#t=" + elapsed;
         if (current["playing"])
@@ -113,11 +116,12 @@ const connect = ()=>{
 
     ws.onmessage = (event)=>{
         const msg = JSON.parse(event.data);
-        if (msg["cmd"] == "play" || msg["cmd"] == "chan_next" || msg["cmd"] == "chan_prev" || msg["cmd"] == "pause")
         {
+            console.log(msg);
+            const prev = current;
             current = msg;
             updateChannelTitle();
-            updatePlayer();
+            updatePlayer(prev);
         }
     };
 
index b45a054a9b9f9e2b5f3e453e4859cc8097dea65c..f75e788811afe18203d5d6fe1135d696a4a9b236 100755 (executable)
@@ -16,9 +16,6 @@ Dir.glob("#{ASSET_DIR}/**/*.*").each do |path|
   FileUtils.cp(path, "#{ATV_ROOT}/#{file}")
 end
 
-#JSON.parse(File.read("#{ATV_ROOT}/config.json"))
-#exit 1
-
 # Then run the server
 db = ATV::Database.new(ATV_ROOT)
 
index 42af495baf27ba6ed313ff77ce41290ed24db231..9a1842fa040bfdf67da07aabd1cffc89da78aa60 100644 (file)
@@ -8,7 +8,7 @@ module ATV
       @time = 0
       @index = 0
       @queue = []
-      @current = items[@index] if items.length > 0
+      @current = files[@index] if files.length > 0
     end
 
 
@@ -29,14 +29,14 @@ module ATV
       if @queue.length > 0
         @current = @queue.shift
       else
-        @index = ((@index+1) >= items.length ? 0 : (@index+1))
-        @current = items[@index]
+        @index = ((@index+1) >= files.length ? 0 : (@index+1))
+        @current = files[@index]
       end
     end
 
     def state()
-       next_index = ((@index+1) >= items.length ? 0 : (@index+1))
-       next_item = (@queue.length > 0 ? @queue.first : items[next_index])
+       next_index = ((@index+1) >= files.length ? 0 : (@index+1))
+       next_item = (@queue.length > 0 ? @queue.first : files[next_index])
       {
         "chan" => @name,
         "time" => @time,
@@ -46,6 +46,7 @@ module ATV
     end
 
     def items()
+#      pp filedata[:tree]
       { "items" => filedata[:tree] }
     end
 
@@ -63,7 +64,7 @@ module ATV
       @db.select_files(@name, @selectors)
     end
 
-    def items()
+    def files()
       filedata[:items].values
     end
   end
index 02f21a7466c6badad76ee3d31034b733bcc4cd9c..eb0d98ebc804493c6c354dcd51add221ac5d568b 100644 (file)
@@ -70,10 +70,12 @@ module ATV
             node[folder] ||= {}
             node = node[folder]
           end
-          node[path.last] = e
+
+          obj = { "path" => e, "duration" => @data[e]["duration"]}
+          node[path.last] = obj
 
           # Return the key/value pair
-          [e, { "path" => e, "duration" => @data[e]["duration"]}]
+          [e, obj]
         end.to_h
 
         # save off the tree