From d7f2f808dff1ec67832c265670fdf57017827d57 Mon Sep 17 00:00:00 2001 From: Ignacio Rivero Date: Fri, 7 Mar 2025 22:54:44 -0300 Subject: [PATCH] Added a D-Pad --- __pycache__/player.cpython-313.pyc | Bin 7358 -> 7358 bytes __pycache__/server.cpython-313.pyc | Bin 16124 -> 19156 bytes player.py | 2 +- server.py | 72 +++++++++---- templates/index.html | 161 ++++++++++++++++++++++------- 5 files changed, 174 insertions(+), 61 deletions(-) diff --git a/__pycache__/player.cpython-313.pyc b/__pycache__/player.cpython-313.pyc index c9c4749b30b7c4271dc5f942645e7c15347b4239..26609cea9304403699b9fb73a373ee0de0263f41 100644 GIT binary patch delta 22 ccmdmIxzCdKGcPX}0}wo2csgBeBkx8T09Ad zff~R+5cIf&pcqA?G^hmYJ5l_@#~(&h7zV7GK%zVZaTH8YQT)E!$2ypg;LY~Sx!?CY z-*Hi9v7|C_ zJ%naSC9I2h^d^oLN`-($QV}io3Zg0U1vx5YcVuzgDhR+XX4%;?b57*DtdM{&WmB67 zrsB92+$N{OTt`>8+wYfqT?74W=j4tVdKbKVdS%y`oz{bvAS&EM z&44k@m@n4A4rpvGV}_N;)dO7F#GTOSj%g|nYbr-HmZ)H%7BCNL(-}R1jeg9TZ@exi z`%_<-H#rp(X|YC3Gn8p^U7yHuSYsK{R7ZvCL?WG|&+YfEm4l)Bo?yV=1^o1*hLHjO zFBJm}b1ReE$8i#ocu9~{ErOL3vzAgF_y@JgK%I$5gC*|%{$5{~JLC)a(@E@@Burx2 zeZD?9^gzEHhK_>SVNO|2qH?;ROgG4`)WVonD-Mmb;3|WK0Gqh)HD$ZH_vIZo)L#@x zZo?=?R1FDbMu)E};BT7I=%RrEzeo1e_xrs!tM7IYXqX`0Z#lj`w^w#RXxax0q5*_{ z1l-4j9(_Vjav3fR6LYdW?X-ldOnWy>3Kjt*l|^gXjvLBRni-{;6VeLO()!%Nb?!C2 z^37?J(wk>YY0C^qLnz^IWf@Z2lvqMSSId~LR4B!PQN{~vpFmi?BiKb{*&plLX~?|mV*QJpFtUT;7;~f5BKJtLgW{<{$L8V5MqftQkMJtOYY49cq!turPud#Efyl=;KVK3S zV(O5adgTz^1Du%Jpm6MYV0J6kU{kTBaTE+n6@^Wx*|(6k&9XG686 z>`}WsJc6UxpF^)KjWE|JbnHA z3ZAVu8Cn1A>>)Gf%@Wmvr3;js$sFpI&8eGUpx>GtWS7SMt$B! z>pKYVA}Hm04@d6<1O)(SPUTpmGQ7mf7j`b`yi<}XTi7@`f1TT`JM53!k7@8Kdft6RGc9`OT}-T9{Er2KD*Vp^-IFX(`2+>R+cQ}r`XMa( z>@|2YN<Td|$Ze;9zm%TFSwAA2LqH)D z2c$ot|1E@72+G1Q;OJ+z%h}AAkVw=yKnl*H=?p?fRCe#28nr?`!?w+7CRLF`a~6@= z6D3zA)ck@Glo^-^N~WAgO56+V*4BBXmThT$ft0YCwl!*Of9hjf+UhE9UpQtK;h132%eo9Y^YdY3RbB<&7T2)m_KJc(a52XazCk#G@HXpd kcTR=;ncpJWt$hK{K(E|RIl?|@Z^-kgNNb!!q!yt51v#1;+W-In delta 1068 zcmZvaTSyd97{}*3GdtCmYZr8Nx3aPgN5k3`FV$u@*>)|0+JYd-%$>G1-Gwu2JwribrYz?ufdv8Dc8WkIPzt zISL~(yDOXb9Cu|U?$zd-4UaA??HW-qoxR%)@zPfOsEh?+1DbN^kra=sVqYoPT(bNMNV2|iF!ri!;j;LRtd+^Svn2}Q7PLD z=gqBUy{x2#Rukk@xE&uR;mD-Q*U}Q>9avOe3>~vR^kR064{l>Y@D4mSp9T+syGIx`dE&S`sH#G^iQ(EBHX`AP+9AJ# zPb1Cqca{Djj|pfF`~EGLq4UFfV_Q=)aX>fH2!(n6d6SxPsA+$z-k4}kC9Sy=qS4HC zJ!u?Fr5pW*Lljyh2DYvXuu;jpwr&hcPYUsC7l;-!_yTGt6aL+xTLH>r;y9s*<_ALxiG>p6Js@N~d z?5t~tyg`y)5dN!*o=}Nun_pl!R0WE8EVKoxr)sWDoaP->ursKUxPSW@+jWN!!gVkU zL*eV-#xLRhxHD1$79Ndw#H;9zc)SfF<%~26%YQ1l%=2?8W4^3fDUbvlj(7@XwlM2d z*_`a`*&8|!PlV-yw>7719>gKeo;d#hf{KT4RX OUzr@EPX- diff --git a/player.py b/player.py index 791c85a..06278ee 100755 --- a/player.py +++ b/player.py @@ -144,7 +144,7 @@ class Player: self.player['video-latency-hacks'] = 'yes' if self.low_latency else 'no' self.player['stream-buffer-size'] = '4k' if self.low_latency else '128k' return self.low_latency - + def stop(self): """Stop the player.""" self.player.stop() diff --git a/server.py b/server.py index a2cb739..74f45fa 100755 --- a/server.py +++ b/server.py @@ -87,7 +87,35 @@ class IPMPVServer: @self.app.route("/toggle_mute") def toggle_mute(): return self._handle_toggle_mute() - + + @self.app.route("/channel_up") + def channel_up(): + return self._handle_channel_up() + + @self.app.route("/channel_down") + def channel_down(): + return self._handle_channel_down() + + @self.app.route('/manifest.json') + def serve_manifest(): + return send_from_directory("static", 'manifest.json', + mimetype='application/manifest+json') + + @self.app.route('/icon512_rounded.png') + def serve_rounded_icon(): + return send_from_directory("static", 'icon512_rounded.png', + mimetype='image/png') + + @self.app.route('/icon512_maskable.png') + def serve_maskable_icon(): + return send_from_directory("static", 'icon512_maskable.png', + mimetype='image/png') + + @self.app.route('/screenshot1.png') + def serve_screenshot_1(): + return send_from_directory("static", 'screenshot1.png', + mimetype='image/png') + def _handle_index(self): """Handle the index route.""" from channels import group_channels @@ -178,6 +206,28 @@ class IPMPVServer: thread.start() return "", 204 + def _handle_channel_up(self): + """Handle the channel_up route.""" + index = self.player.current_index + 1 if self.player.current_index is not None else 0; + thread = threading.Thread( + target=self.player.play_channel, + args=(index,self.channels), + daemon=True + ) + thread.start() + return "", 204 + + def _handle_channel_down(self): + """Handle the channel_down route.""" + index = self.player.current_index - 1 if self.player.current_index is not None else -1; + thread = threading.Thread( + target=self.player.play_channel, + args=(index,self.channels), + daemon=True + ) + thread.start() + return "", 204 + def _handle_toggle_deinterlace(self): """Handle the toggle_deinterlace route.""" state = self.player.toggle_deinterlace() @@ -219,26 +269,6 @@ class IPMPVServer: self.resolution = change_resolution(self.resolution) return jsonify(res=self.resolution) - @self.app.route('/manifest.json') - def serve_manifest(): - return send_from_directory("static", 'manifest.json', - mimetype='application/manifest+json') - - @self.app.route('/icon512_rounded.png') - def serve_rounded_icon(): - return send_from_directory("static", 'icon512_rounded.png', - mimetype='image/png') - - @self.app.route('/icon512_maskable.png') - def serve_maskable_icon(): - return send_from_directory("static", 'icon512_maskable.png', - mimetype='image/png') - - @self.app.route('/screenshot1.png') - def serve_screenshot_1(): - return send_from_directory("static", 'screenshot1.png', - mimetype='image/png') - def _handle_volume_up(self): """Handle the volume_up route.""" if self.volume_control: diff --git a/templates/index.html b/templates/index.html index 4596df9..5b406c1 100644 --- a/templates/index.html +++ b/templates/index.html @@ -37,6 +37,74 @@ line-height: 1.4; } + .dpad-container { + position: relative; + width: 240px; + height: 240px; + } + + .dpad-container::after { + position: absolute; + top: 80px; + left: 80px; + content: ""; + background-color: var(--secondary-bg); + color: white; + width: 80px; + height: 80px; + } + + .dpad-button { + position: absolute; + min-width: 80px; + min-height: 80px; + margin: 0; + color: white; + display: flex; + justify-content: center; + align-items: center; + cursor: pointer; + user-select: none; + transition: background-color 0.2s, transform 0.1s; + } + + .dpad-button:hover { + background-color: #555; + } + + .dpad-button:active { + background-color: #777; + transform: scale(0.95); + } + + #dpad-up { + top: 0; + left: 80px; + border-radius: 10px 10px 0 0; + border-bottom: 0; + } + + #dpad-right { + top: 80px; + right: 0; + border-radius: 0 10px 10px 0; + border-left: 0; + } + + #dpad-down { + bottom: 0; + left: 80px; + border-radius: 0 0 10px 10px; + border-top: 0; + } + + #dpad-left { + top: 80px; + left: 0; + border-radius: 10px 0 0 10px; + border-right: 0; + } + .container { max-width: 1200px; margin: 0 auto; @@ -229,6 +297,9 @@ .section { margin: 20px 0; padding: 10px; + display: flex; + flex-direction: column; + align-items: center; border-radius: var(--border-radius); } @@ -276,7 +347,7 @@ /* Mobile touch improvements */ @media (max-width: 767px) { - button, + button:not(.dpad-button,.leftbtn,.rightbtn,.midbtn), input { padding: 14px; /* Larger touch targets */ @@ -346,18 +417,9 @@ margin-top: 8px; } - #osd-on-btn { - margin-top: 8px; - margin-bottom: 8px; - margin-left: 4px; - min-width: 100px; - } - - #osd-off-btn { - margin-top: 8px; - margin-bottom: 8px; - margin-right: 4px; - min-width: 100px; + .leftbtn, .rightbtn, .midbtn { + width: 90%; + padding: 14px; } } @@ -368,6 +430,15 @@

Welcome to IPMPV

Current Channel: %CURRENT_CHANNEL%

+
+
+ + + + +
+
+
-
+

Volume

- + - +
@@ -532,30 +603,42 @@ fetch(`/hide_osd`).then(response => response.json()); } - function volumeUp() { - fetch(`/volume_up`) - .then(response => response.json()) - .then(data => { - showToast("Volume: "+data.volume+"%"); - }); - } + function volumeUp() { + fetch(`/volume_up`) + .then(response => response.json()) + .then(data => { + showToast("Volume: " + data.volume + "%"); + }); + } - function volumeDown() { - fetch(`/volume_down`) - .then(response => response.json()) - .then(data => { - showToast("Volume: "+data.volume+"%"); - }); - } + function volumeDown() { + fetch(`/volume_down`) + .then(response => response.json()) + .then(data => { + showToast("Volume: " + data.volume + "%"); + }); + } - function toggleMute() { - fetch(`/toggle_mute`) - .then(response => response.json()) - .then(data => { - muted = data.muted ? "yes" : "no"; - showToast("Muted: " + muted); - }); - } + function toggleMute() { + fetch(`/toggle_mute`) + .then(response => response.json()) + .then(data => { + muted = data.muted ? "yes" : "no"; + showToast("Muted: " + muted); + }); + } + + function channelUp() { + showToast("Loading channel..."); + fetch(`/channel_up`) + .then(() => window.location.reload()) + } + + function channelDown() { + showToast("Loading channel..."); + fetch(`/channel_down`) + .then(() => window.location.reload()) + } // Mobile-friendly toast notification function showToast(message) { @@ -586,4 +669,4 @@ - + \ No newline at end of file