ds-play created
This commit is contained in:
BIN
app/static/__pycache__/functions.cpython-312.pyc
Normal file
BIN
app/static/__pycache__/functions.cpython-312.pyc
Normal file
Binary file not shown.
160
app/static/functions.py
Normal file
160
app/static/functions.py
Normal file
@@ -0,0 +1,160 @@
|
||||
import os
|
||||
import json
|
||||
import requests
|
||||
import logging
|
||||
|
||||
# Configure logging
|
||||
logging.basicConfig(level=logging.INFO)
|
||||
Logger = logging.getLogger(__name__)
|
||||
|
||||
def load_playlist():
|
||||
"""Load playlist from the server or local storage."""
|
||||
try:
|
||||
Logger.info("python_functions: Attempting to load playlist from server...")
|
||||
|
||||
# Load app configuration
|
||||
app_config_path = os.path.join(os.path.dirname(__file__), '../../app_config.json')
|
||||
if not os.path.exists(app_config_path):
|
||||
Logger.error("python_functions: App configuration file not found.")
|
||||
return []
|
||||
|
||||
with open(app_config_path, 'r') as config_file:
|
||||
app_config = json.load(config_file)
|
||||
|
||||
server = app_config.get('server_address', '')
|
||||
port = app_config.get('port', 1025)
|
||||
host = app_config.get('player_name', '')
|
||||
quick = app_config.get('quickconnect_code', '')
|
||||
|
||||
if not server or not host or not quick:
|
||||
Logger.error("python_functions: Missing required configuration values.")
|
||||
return []
|
||||
|
||||
# Construct the server IP and request URL
|
||||
server_ip = f'{server}:{port}'
|
||||
url = f'http://{server_ip}/api/playlists'
|
||||
params = {
|
||||
'hostname': host,
|
||||
'quickconnect_code': quick
|
||||
}
|
||||
|
||||
# Send the request
|
||||
response = requests.get(url, params=params)
|
||||
|
||||
# Debugging logs
|
||||
Logger.debug(f"python_functions: Status Code: {response.status_code}")
|
||||
Logger.debug(f"python_functions: Response Content: {response.text}")
|
||||
|
||||
if response.status_code == 200:
|
||||
try:
|
||||
playlist = response.json().get('playlist', [])
|
||||
Logger.info("python_functions: Playlist loaded successfully.")
|
||||
Logger.debug(f"python_functions: Loaded playlist: {playlist}")
|
||||
return playlist
|
||||
except json.JSONDecodeError as e:
|
||||
Logger.error(f"python_functions: Failed to parse JSON response: {e}")
|
||||
else:
|
||||
Logger.error(f"python_functions: Failed to retrieve playlist: {response.text}")
|
||||
except requests.exceptions.RequestException as e:
|
||||
Logger.error(f"python_functions: Failed to load playlist: {e}")
|
||||
return []
|
||||
|
||||
def download_media_files(playlist):
|
||||
"""Download media files from the playlist."""
|
||||
Logger.info("python_functions: Starting media file download...")
|
||||
base_dir = os.path.join(os.path.dirname(__file__), 'static', 'resurse') # Update this to the correct path
|
||||
if not os.path.exists(base_dir):
|
||||
os.makedirs(base_dir)
|
||||
Logger.info(f"python_functions: Created directory {base_dir} for media files.")
|
||||
|
||||
for media in playlist:
|
||||
file_name = media.get('file_name', '')
|
||||
file_url = media.get('url', '')
|
||||
file_path = os.path.join(base_dir, file_name)
|
||||
|
||||
try:
|
||||
response = requests.get(file_url)
|
||||
if response.status_code == 200:
|
||||
with open(file_path, 'wb') as file:
|
||||
file.write(response.content)
|
||||
Logger.info(f"python_functions: Downloaded {file_name} to {file_path}")
|
||||
else:
|
||||
Logger.error(f"python_functions: Failed to download {file_name}: {response.status_code}")
|
||||
except requests.exceptions.RequestException as e:
|
||||
Logger.error(f"python_functions: Failed to download {file_name}: {e}")
|
||||
|
||||
def download_playlist_files_from_server():
|
||||
"""Download playlist files using app configuration."""
|
||||
Logger.info("python_functions: Starting playlist file download using app configuration...")
|
||||
|
||||
# Load app configuration
|
||||
app_config_path = os.path.join(os.path.dirname(__file__), '../../app_config.json')
|
||||
if not os.path.exists(app_config_path):
|
||||
Logger.error("python_functions: App configuration file not found.")
|
||||
return
|
||||
|
||||
try:
|
||||
with open(app_config_path, 'r') as config_file:
|
||||
app_config = json.load(config_file)
|
||||
except json.JSONDecodeError as e:
|
||||
Logger.error(f"python_functions: Failed to load app configuration: {e}")
|
||||
return
|
||||
|
||||
# Extract configuration values
|
||||
server_address = app_config.get('server_address', '')
|
||||
port = app_config.get('port', 1025)
|
||||
hostname = app_config.get('player_name', '')
|
||||
quickconnect_code = app_config.get('quickconnect_code', '')
|
||||
|
||||
if not server_address or not hostname or not quickconnect_code:
|
||||
Logger.error("python_functions: Missing required configuration values.")
|
||||
return
|
||||
|
||||
# Construct the request URL and parameters
|
||||
server_ip = f'{server_address}:{port}'
|
||||
url = f'http://{server_ip}/api/playlists'
|
||||
params = {
|
||||
'hostname': hostname,
|
||||
'quickconnect_code': quickconnect_code
|
||||
}
|
||||
|
||||
try:
|
||||
# Send request to fetch the playlist
|
||||
response = requests.get(url, params=params)
|
||||
Logger.debug(f"python_functions: Status Code: {response.status_code}")
|
||||
Logger.debug(f"python_functions: Response Content: {response.text}")
|
||||
|
||||
if response.status_code == 200:
|
||||
try:
|
||||
playlist = response.json().get('playlist', [])
|
||||
Logger.info("python_functions: Playlist retrieved successfully.")
|
||||
Logger.debug(f"python_functions: Playlist: {playlist}")
|
||||
|
||||
# Download media files from the playlist
|
||||
base_dir = os.path.join(os.path.dirname(__file__), 'static', 'resurse')
|
||||
if not os.path.exists(base_dir):
|
||||
os.makedirs(base_dir)
|
||||
Logger.info(f"python_functions: Created directory {base_dir} for media files.")
|
||||
|
||||
for media in playlist:
|
||||
file_name = media.get('file_name', '')
|
||||
file_url = media.get('url', '')
|
||||
file_path = os.path.join(base_dir, file_name)
|
||||
|
||||
try:
|
||||
file_response = requests.get(file_url)
|
||||
if file_response.status_code == 200:
|
||||
with open(file_path, 'wb') as file:
|
||||
file.write(file_response.content)
|
||||
Logger.info(f"python_functions: Downloaded {file_name} to {file_path}")
|
||||
else:
|
||||
Logger.error(f"python_functions: Failed to download {file_name}: {file_response.status_code}")
|
||||
except requests.exceptions.RequestException as e:
|
||||
Logger.error(f"python_functions: Failed to download {file_name}: {e}")
|
||||
except json.JSONDecodeError as e:
|
||||
Logger.error(f"python_functions: Failed to parse playlist JSON: {e}")
|
||||
else:
|
||||
Logger.error(f"python_functions: Failed to retrieve playlist: {response.text}")
|
||||
except requests.exceptions.RequestException as e:
|
||||
Logger.error(f"python_functions: Failed to connect to server: {e}")
|
||||
download_playlist_files_from_server()
|
||||
182
app/static/index.html
Normal file
182
app/static/index.html
Normal file
@@ -0,0 +1,182 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Media Player</title>
|
||||
<style>
|
||||
body {
|
||||
font-family: Arial, sans-serif;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
background-color: black;
|
||||
color: white;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
height: 100vh;
|
||||
}
|
||||
.playlist-container {
|
||||
flex: 1;
|
||||
overflow: hidden;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
background-color: black;
|
||||
}
|
||||
ul {
|
||||
list-style-type: none;
|
||||
padding: 0;
|
||||
display: none; /* Hide the playlist list */
|
||||
}
|
||||
.controls-wrapper {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
width: 33.33%; /* 1/3 of the page width */
|
||||
margin: 0 auto;
|
||||
transition: opacity 0.5s ease; /* Smooth fade effect */
|
||||
}
|
||||
.controls-wrapper.hidden {
|
||||
opacity: 0; /* Hide the buttons */
|
||||
pointer-events: none; /* Disable interaction when hidden */
|
||||
}
|
||||
.controls {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
gap: 15px; /* Space between buttons */
|
||||
padding: 10px;
|
||||
background-color: #222;
|
||||
}
|
||||
button {
|
||||
margin: 5px;
|
||||
padding: 10px;
|
||||
background-color: #444;
|
||||
color: white;
|
||||
border: none;
|
||||
border-radius: 5px;
|
||||
cursor: pointer;
|
||||
font-size: 20px;
|
||||
}
|
||||
button:hover {
|
||||
background-color: #666;
|
||||
}
|
||||
button i {
|
||||
pointer-events: none;
|
||||
}
|
||||
img, video {
|
||||
max-width: 100%;
|
||||
max-height: 100%;
|
||||
}
|
||||
</style>
|
||||
<!-- Add Font Awesome for icons -->
|
||||
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0-beta3/css/all.min.css">
|
||||
</head>
|
||||
<body>
|
||||
<div class="playlist-container" id="playlist-container">
|
||||
<!-- Content will be dynamically added here -->
|
||||
</div>
|
||||
<div class="controls-wrapper" id="controls-wrapper">
|
||||
<div class="controls">
|
||||
<button onclick="previousMedia()"><i class="fas fa-step-backward"></i></button> <!-- Previous -->
|
||||
<button onclick="loadPlaylist()"><i class="fas fa-sync-alt"></i></button> <!-- Refresh Playlist -->
|
||||
<button onclick="playMedia()"><i class="fas fa-play"></i></button> <!-- Play -->
|
||||
<button onclick="nextMedia()"><i class="fas fa-step-forward"></i></button> <!-- Next -->
|
||||
<button onclick="stopMedia()"><i class="fas fa-stop"></i></button> <!-- Stop -->
|
||||
<button onclick="goToSettings()"><i class="fas fa-cog"></i></button> <!-- Settings -->
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
const apiBase = 'http://localhost:1025'; // Update to match your Flask app's port
|
||||
const playlistContainer = document.getElementById('playlist-container');
|
||||
let playlist = [];
|
||||
let currentIndex = 0;
|
||||
let playbackInterval;
|
||||
|
||||
// Function to load the playlist from updated_playlist.json
|
||||
async function loadPlaylist() {
|
||||
try {
|
||||
const response = await fetch(`${apiBase}/updated_playlist.json`);
|
||||
if (!response.ok) {
|
||||
throw new Error(`Failed to load playlist: ${response.statusText}`);
|
||||
}
|
||||
const data = await response.json();
|
||||
playlist = data; // Use the updated playlist
|
||||
console.log("Loaded playlist:", playlist); // Debug log
|
||||
startPlaylist(); // Start playing the playlist after loading
|
||||
} catch (error) {
|
||||
console.error("Error loading playlist:", error);
|
||||
}
|
||||
}
|
||||
|
||||
// Function to start playing the playlist
|
||||
function startPlaylist() {
|
||||
if (playlist.length === 0) {
|
||||
console.error("No items in the playlist.");
|
||||
return;
|
||||
}
|
||||
playCurrentItem();
|
||||
}
|
||||
|
||||
// Function to play the current item in the playlist
|
||||
function playCurrentItem() {
|
||||
if (currentIndex >= playlist.length) {
|
||||
currentIndex = 0; // Loop back to the beginning
|
||||
}
|
||||
|
||||
const currentItem = playlist[currentIndex];
|
||||
playlistContainer.innerHTML = ''; // Clear the container
|
||||
|
||||
if (currentItem.type === 'image') {
|
||||
const img = document.createElement('img');
|
||||
img.src = currentItem.url;
|
||||
playlistContainer.appendChild(img);
|
||||
|
||||
// Display the image for the specified duration
|
||||
playbackInterval = setTimeout(() => {
|
||||
currentIndex++;
|
||||
playCurrentItem();
|
||||
}, currentItem.duration * 1000);
|
||||
} else if (currentItem.type === 'video') {
|
||||
const video = document.createElement('video');
|
||||
video.src = currentItem.url;
|
||||
video.autoplay = true;
|
||||
video.controls = false;
|
||||
playlistContainer.appendChild(video);
|
||||
|
||||
// Play the video and move to the next item after it ends
|
||||
video.onended = () => {
|
||||
currentIndex++;
|
||||
playCurrentItem();
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
// Function to stop playback
|
||||
function stopMedia() {
|
||||
clearTimeout(playbackInterval);
|
||||
playlistContainer.innerHTML = ''; // Clear the container
|
||||
}
|
||||
|
||||
// Function to play the previous item
|
||||
function previousMedia() {
|
||||
stopMedia();
|
||||
currentIndex = (currentIndex - 1 + playlist.length) % playlist.length;
|
||||
playCurrentItem();
|
||||
}
|
||||
|
||||
// Function to play the next item
|
||||
function nextMedia() {
|
||||
stopMedia();
|
||||
currentIndex = (currentIndex + 1) % playlist.length;
|
||||
playCurrentItem();
|
||||
}
|
||||
|
||||
function goToSettings() {
|
||||
window.location.href = '/static/settings.html';
|
||||
}
|
||||
|
||||
// Load playlist on page load
|
||||
loadPlaylist();
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
BIN
app/static/resurse/IMG_20250503_220547.jpg
Normal file
BIN
app/static/resurse/IMG_20250503_220547.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 3.6 MiB |
BIN
app/static/resurse/IMG_20250506_080609.jpg
Normal file
BIN
app/static/resurse/IMG_20250506_080609.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 4.3 MiB |
BIN
app/static/resurse/VID_20250501_184228.mp4
Normal file
BIN
app/static/resurse/VID_20250501_184228.mp4
Normal file
Binary file not shown.
1
app/static/resurse/playlist.json
Normal file
1
app/static/resurse/playlist.json
Normal file
@@ -0,0 +1 @@
|
||||
[{"duration": 15, "file_name": "IMG_20250503_220547.jpg", "url": "http://digi-signage.moto-adv.com/media/IMG_20250503_220547.jpg"}, {"duration": 15, "file_name": "IMG_20250506_080609.jpg", "url": "http://digi-signage.moto-adv.com/media/IMG_20250506_080609.jpg"}, {"duration": 15, "file_name": "VID_20250501_184228.mp4", "url": "http://digi-signage.moto-adv.com/media/VID_20250501_184228.mp4"}]
|
||||
84
app/static/settings.html
Normal file
84
app/static/settings.html
Normal file
@@ -0,0 +1,84 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Settings</title>
|
||||
<style>
|
||||
body { font-family: Arial, sans-serif; margin: 20px; }
|
||||
label { display: block; margin: 10px 0 5px; }
|
||||
input, select { width: 100%; padding: 8px; margin-bottom: 10px; }
|
||||
button { padding: 10px 15px; }
|
||||
.home-button { position: fixed; bottom: 20px; right: 20px; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<h1>Settings</h1>
|
||||
<form id="settings-form">
|
||||
<label for="player_orientation">Player Orientation</label>
|
||||
<select id="player_orientation" name="player_orientation">
|
||||
<option value="portrait">Portrait</option>
|
||||
<option value="landscape">Landscape</option>
|
||||
</select>
|
||||
|
||||
<label for="player_name">Player Name</label>
|
||||
<input type="text" id="player_name" name="player_name">
|
||||
|
||||
<label for="quickconnect_code">QuickConnect Code</label>
|
||||
<input type="text" id="quickconnect_code" name="quickconnect_code">
|
||||
|
||||
<label for="server_address">Server Address</label>
|
||||
<input type="text" id="server_address" name="server_address">
|
||||
|
||||
<label for="port">Port</label>
|
||||
<input type="number" id="port" name="port">
|
||||
|
||||
<button type="button" onclick="saveConfig()">Save Settings</button>
|
||||
</form>
|
||||
|
||||
<!-- Home button -->
|
||||
<button class="home-button" onclick="goHome()">🏠 Home</button>
|
||||
|
||||
<script>
|
||||
const apiBase = '/api';
|
||||
|
||||
// Load configuration on page load
|
||||
async function loadConfig() {
|
||||
const response = await fetch(`${apiBase}/config`);
|
||||
const config = await response.json();
|
||||
document.getElementById('player_orientation').value = config.player_orientation;
|
||||
document.getElementById('player_name').value = config.player_name;
|
||||
document.getElementById('quickconnect_code').value = config.quickconnect_code;
|
||||
document.getElementById('server_address').value = config.server_address;
|
||||
document.getElementById('port').value = config.port;
|
||||
}
|
||||
|
||||
// Save configuration
|
||||
async function saveConfig() {
|
||||
const config = {
|
||||
player_orientation: document.getElementById('player_orientation').value,
|
||||
player_name: document.getElementById('player_name').value,
|
||||
quickconnect_code: document.getElementById('quickconnect_code').value,
|
||||
server_address: document.getElementById('server_address').value,
|
||||
port: parseInt(document.getElementById('port').value, 10)
|
||||
};
|
||||
|
||||
await fetch(`${apiBase}/config`, {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify(config)
|
||||
});
|
||||
|
||||
alert('Settings saved successfully!');
|
||||
}
|
||||
|
||||
// Navigate back to the home page
|
||||
function goHome() {
|
||||
window.location.href = '/';
|
||||
}
|
||||
|
||||
// Load configuration when the page loads
|
||||
loadConfig();
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
Reference in New Issue
Block a user