--- /dev/null
+*.json
\ No newline at end of file
--- /dev/null
+<!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: #000;
+ overflow: hidden;
+ }
+ </style>
+</head>
+<body>
+<video id="Video" width="100%" height="100%" src="" autoplay muted playsinline>
+</video>
+
+ <script>
+ (()=>{
+ let program = {};
+
+ const secondsSinceMidnight = ()=>{
+ const now = new Date();
+ const midnight = new Date(
+ now.getFullYear(),
+ now.getMonth(),
+ now.getDate(),
+ 0,0,0);
+ return (now.getTime() - midnight.getTime()) / 1000;
+ };
+
+ const playNextItem = (force)=>{
+ const channel = program["channels"][program["curr"]];
+ console.log(channel);
+ const duration = channel["curr"]["duration"];
+ const since_mn = secondsSinceMidnight();
+ const end_time = channel["start_time"] + duration;
+ const offset = duration - (end_time - since_mn);
+ console.log(offset);
+ if (force || (offset > 0 && offset < duration))
+ {
+ Video.src = channel["curr"]["path"] + "#t=" + Math.floor(offset);
+ console.log(Video.src);
+ }
+ };
+
+ const updateProgram = (onload)=>{
+ fetch("program.json")
+ .then((resp)=> resp.json() )
+ .then((json)=>{
+ let changed = (
+ program["curr"] !== json["curr"]
+ );
+ program = json;
+ if (typeof onload === 'function') {
+ onload();
+ } else if (changed) {
+ playNextItem();
+ }
+ });
+ };
+
+ const updateCurrentItem = ()=>{
+ updateProgram(playNextItem);
+ };
+
+ /* start the program and setup listeners to keep it in sync */
+ updateCurrentItem();
+ Video.addEventListener("ended", updateCurrentItem);
+ document.addEventListener("visibilitychange", updateCurrentItem);
+ setInterval(updateProgram, 1000);
+})();
+ </script>
+
+</body></html>
\ No newline at end of file
--- /dev/null
+#!/bin/env ruby
+
+require 'json'
+require 'fileutils'
+
+# reload the database
+$db = JSON.parse(File.read("index.json"))
+$channels = [
+ { include: ["Christmas"] },
+ { include: ["Movies", "Shorts", "Shows"] },
+ { include: ["Movies"] },
+ { include: ["Shorts"] },
+ { include: ["Shows"] },
+ { include: ["Pictures"] },
+]
+
+def secs_since_midnight()
+ now = Time.now
+ midnight = Time.new(now.year, now.month, now.day, 0, 0, 0)
+ (now - midnight)
+end
+
+def next_show(channel)
+ cfg = $channels[channel]
+ path = cfg[:files][cfg[:curr]]
+ cfg[:curr] += 1
+ if cfg[:curr] >= cfg[:files].length
+ cfg[:files].shuffle
+ cfg[:curr] = 0
+ end
+ (path ? $db[path].merge({"path"=>path}) : nil)
+end
+
+def populate_channels()
+ files = $db.keys
+ start_time = secs_since_midnight()
+ $channels.each_with_index do |cfg, i|
+ cfg[:files] = files.select{|f| cfg[:include].select{|prefix| f.start_with? prefix}.length > 0 }.shuffle
+ cfg[:curr] = 0
+ cfg[:play] = {
+ chan: i,
+ start_time: start_time,
+ }
+ cfg[:play][:curr] = next_show(i)
+ cfg[:play][:next] = next_show(i)
+ end
+end
+
+def update_program()
+ data = { "curr" => 1, "channels" => $channels.map{|c| c[:play] } }
+ pp data
+ File.open("program.json.temp", "wb") do |f|
+ f.write JSON.dump(data)
+ end
+ FileUtils.mv("program.json.temp", "program.json")
+end
+
+def program_changed()
+ changed = false
+ time = secs_since_midnight
+# time = $channels[0][:play][:start_time] + $channels[0][:play][:curr]["duration"]
+ $channels.each_with_index do |chan, i|
+ next if not chan[:play][:curr]
+ end_time = chan[:play][:start_time] + chan[:play][:curr]["duration"]
+ if end_time <= time then
+ changed = true
+ chan[:play][:chan] = i
+ chan[:play][:curr] = chan[:play][:next]
+ chan[:play][:next] = next_show(i)
+ chan[:play][:start_time] = time
+# pp chan[:play]
+ end
+# puts "Chan #{i} time remaining #{end_time - time}"
+ end
+ changed
+end
+
+# load up the channels and generate the initial program
+populate_channels()
+update_program()
+
+# wake up once a second to see if a program has finished and pick the next program
+while 1
+ if program_changed()
+ update_program()
+ end
+ sleep 1
+end
+
--- /dev/null
+<!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: #000;
+ overflow: hidden;
+ }
+ </style>
+</head>
+<body>
+<video id="Video" width="100%" height="100%" src="" autoplay muted playsinline>
+</video>
+
+ <script>
+ (()=>{
+ let program = {};
+
+ const secondsSinceMidnight = ()=>{
+ const now = new Date();
+ const midnight = new Date(
+ now.getFullYear(),
+ now.getMonth(),
+ now.getDate(),
+ 0,0,0);
+ return (now.getTime() - midnight.getTime()) / 1000;
+ };
+
+ const playNextItem = (force)=>{
+ const channel = program["channels"][program["curr"]];
+ console.log(channel);
+ const duration = channel["curr"]["duration"];
+ const since_mn = secondsSinceMidnight();
+ const end_time = channel["start_time"] + duration;
+ const offset = duration - (end_time - since_mn);
+ console.log(offset);
+ if (force || (offset > 0 && offset < duration))
+ {
+ Video.src = channel["curr"]["path"] + "#t=" + Math.floor(offset);
+ console.log(Video.src);
+ }
+ };
+
+ const updateProgram = (onload)=>{
+ fetch("program.json")
+ .then((resp)=> resp.json() )
+ .then((json)=>{
+ let changed = (
+ program["curr"] !== json["curr"]
+ );
+ program = json;
+ if (typeof onload === 'function') {
+ onload();
+ } else if (changed) {
+ playNextItem();
+ }
+ });
+ };
+
+ const updateCurrentItem = ()=>{
+ updateProgram(playNextItem);
+ };
+
+ /* start the program and setup listeners to keep it in sync */
+ updateCurrentItem();
+ Video.addEventListener("ended", updateCurrentItem);
+ document.addEventListener("visibilitychange", updateCurrentItem);
+ setInterval(updateProgram, 1000);
+})();
+ </script>
+
+</body></html>
\ No newline at end of file
--- /dev/null
+#!/bin/env ruby
+
+require 'json'
+require 'fileutils'
+
+CMD="ffprobe -show_entries format=duration -v quiet -of csv=\"p=0\" -i"
+
+# reload the database
+$db = {}
+if File.exist?("index.json")
+ $db = JSON.parse(File.read("index.json"))
+end
+
+def cleanup_files()
+ $db.keys.each do |f|
+ if not File.exist? f then
+ $db.delete(f)
+ end
+ end
+end
+
+def scan_pictures(root_path)
+end
+
+#def scan_shows(root_path)
+#end
+
+def scan_videos(root_path)
+ Dir.glob("#{root_path}/**/*.{mp4,webm,ogg}").each do |f|
+ next if $db[f]
+ puts f
+ duration = `#{CMD} \"#{f}\"`.chomp.to_f
+ $db[f] = { "duration" => duration }
+ end
+end
+
+# scan for pictures and videos
+cleanup_files()
+scan_pictures("Pictures")
+scan_videos("Christmas")
+scan_videos("Movies")
+scan_videos("Shorts")
+scan_videos("Shows")
+
+# write the database to disk
+File.open("index.json.temp", "wb") do |f|
+ f.write JSON.dump($db)
+end
+FileUtils.mv("index.json.temp", "index.json")
--- /dev/null
+#!/bin/sh
+
+play_videos(){
+ while [ 1 ]; do
+ ./play-videos
+ done
+}
+
+play_videos &
+websocketd -staticdir . -port 8080 cat
--- /dev/null
+#!/bin/env ruby
+
+require 'iodine'
+require 'fileutils'
+
+APP = Proc.new do |env|
+# if env['rack.upgrade?'.freeze] == :websocket
+# env['rack.upgrade'.freeze] = SystemTrace
+# [0,{}, []] # It's possible to set cookies for the response.
+# else
+# data = PAGE_HTML
+# [200, {"Content-Length" => "#{data.length}", "Content-Type" => "text/html"}, [data]]
+# end
+end
+
+
+# static file service
+Iodine.listen(
+ service: :http,
+ public: Dir.getwd(),
+ handler: APP
+)
+
+# for static file service, we only need a single thread and a single worker.
+Iodine.threads = 1
+Iodine.start