Compare commits
No commits in common. "80a4286bc0272113e509bf61c32de520d1bc24b0" and "d2e6e2a7d710271d7ed6a2a89e4513c85df15b63" have entirely different histories.
80a4286bc0
...
d2e6e2a7d7
|
|
@ -19,18 +19,10 @@
|
||||||
* {
|
* {
|
||||||
-webkit-overflow-scrolling: touch;
|
-webkit-overflow-scrolling: touch;
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
user-select: none;
|
|
||||||
-webkit-user-select: none;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
html {
|
html {
|
||||||
scroll-behavior: smooth;
|
scroll-behavior: smooth;
|
||||||
overflow: hidden;
|
|
||||||
scrollbar-width: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
html::-webkit-scrollbar {
|
|
||||||
display: none;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
body {
|
body {
|
||||||
|
|
|
||||||
31
script.js
31
script.js
|
|
@ -1274,22 +1274,6 @@ function updateCrossfader(val) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Library Functions
|
// Library Functions
|
||||||
// ---------------------------------------------------------------------------
|
|
||||||
// Tauri v2 asset-protocol helper
|
|
||||||
// When running inside Tauri (window.__TAURI__ is injected via withGlobalTauri)
|
|
||||||
// and the server has provided an absolutePath for the track, we convert it to
|
|
||||||
// an asset:// URL so the WebView reads the file directly from disk — no Flask
|
|
||||||
// round-trip, works with any folder under $HOME.
|
|
||||||
// Falls back to the ordinary server URL when not in Tauri or no absolutePath.
|
|
||||||
// ---------------------------------------------------------------------------
|
|
||||||
function tauriResolve(track) {
|
|
||||||
const cvt = window.__TAURI__?.core?.convertFileSrc;
|
|
||||||
if (cvt && track.absolutePath) {
|
|
||||||
return cvt(track.absolutePath);
|
|
||||||
}
|
|
||||||
return track.file;
|
|
||||||
}
|
|
||||||
|
|
||||||
async function fetchLibrary() {
|
async function fetchLibrary() {
|
||||||
try {
|
try {
|
||||||
const res = await fetch('library.json?t=' + new Date().getTime());
|
const res = await fetch('library.json?t=' + new Date().getTime());
|
||||||
|
|
@ -1319,7 +1303,7 @@ function renderLibrary(songs) {
|
||||||
|
|
||||||
// Drag data
|
// Drag data
|
||||||
item.ondragstart = (e) => {
|
item.ondragstart = (e) => {
|
||||||
e.dataTransfer.setData('trackFile', tauriResolve(t));
|
e.dataTransfer.setData('trackFile', t.file);
|
||||||
e.dataTransfer.setData('trackTitle', t.title);
|
e.dataTransfer.setData('trackTitle', t.title);
|
||||||
e.dataTransfer.setData('source', 'library');
|
e.dataTransfer.setData('source', 'library');
|
||||||
item.classList.add('dragging');
|
item.classList.add('dragging');
|
||||||
|
|
@ -1340,25 +1324,25 @@ function renderLibrary(songs) {
|
||||||
const btnA = document.createElement('button');
|
const btnA = document.createElement('button');
|
||||||
btnA.className = 'load-btn btn-a';
|
btnA.className = 'load-btn btn-a';
|
||||||
btnA.textContent = 'LOAD A';
|
btnA.textContent = 'LOAD A';
|
||||||
btnA.addEventListener('click', () => loadFromServer('A', tauriResolve(t), t.title));
|
btnA.addEventListener('click', () => loadFromServer('A', t.file, t.title));
|
||||||
|
|
||||||
const btnB = document.createElement('button');
|
const btnB = document.createElement('button');
|
||||||
btnB.className = 'load-btn btn-b';
|
btnB.className = 'load-btn btn-b';
|
||||||
btnB.textContent = 'LOAD B';
|
btnB.textContent = 'LOAD B';
|
||||||
btnB.addEventListener('click', () => loadFromServer('B', tauriResolve(t), t.title));
|
btnB.addEventListener('click', () => loadFromServer('B', t.file, t.title));
|
||||||
|
|
||||||
// QUEUE buttons
|
// QUEUE buttons
|
||||||
const queueA = document.createElement('button');
|
const queueA = document.createElement('button');
|
||||||
queueA.className = 'load-btn queue-btn-a';
|
queueA.className = 'load-btn queue-btn-a';
|
||||||
queueA.textContent = 'Q-A';
|
queueA.textContent = 'Q-A';
|
||||||
queueA.title = 'Add to Queue A';
|
queueA.title = 'Add to Queue A';
|
||||||
queueA.addEventListener('click', () => addToQueue('A', tauriResolve(t), t.title));
|
queueA.addEventListener('click', () => addToQueue('A', t.file, t.title));
|
||||||
|
|
||||||
const queueB = document.createElement('button');
|
const queueB = document.createElement('button');
|
||||||
queueB.className = 'load-btn queue-btn-b';
|
queueB.className = 'load-btn queue-btn-b';
|
||||||
queueB.textContent = 'Q-B';
|
queueB.textContent = 'Q-B';
|
||||||
queueB.title = 'Add to Queue B';
|
queueB.title = 'Add to Queue B';
|
||||||
queueB.addEventListener('click', () => addToQueue('B', tauriResolve(t), t.title));
|
queueB.addEventListener('click', () => addToQueue('B', t.file, t.title));
|
||||||
|
|
||||||
loadActions.appendChild(btnA);
|
loadActions.appendChild(btnA);
|
||||||
loadActions.appendChild(queueA);
|
loadActions.appendChild(queueA);
|
||||||
|
|
@ -1367,9 +1351,8 @@ function renderLibrary(songs) {
|
||||||
item.appendChild(trackName);
|
item.appendChild(trackName);
|
||||||
item.appendChild(loadActions);
|
item.appendChild(loadActions);
|
||||||
|
|
||||||
// Add data attribute for highlighting — store the resolved URL so
|
// Add data attribute for highlighting
|
||||||
// updateLibraryHighlighting() matches decks.X.currentFile correctly.
|
item.dataset.file = t.file;
|
||||||
item.dataset.file = tauriResolve(t);
|
|
||||||
|
|
||||||
list.appendChild(item);
|
list.appendChild(item);
|
||||||
});
|
});
|
||||||
|
|
|
||||||
101
server.py
101
server.py
|
|
@ -54,15 +54,6 @@ broadcast_state = {
|
||||||
listener_sids = set()
|
listener_sids = set()
|
||||||
dj_sids = set()
|
dj_sids = set()
|
||||||
|
|
||||||
# Set to True once the first audio chunk arrives in MP3-direct mode.
|
|
||||||
# broadcast_started is held back until then so listeners don't connect
|
|
||||||
# to /stream.mp3 before any data is flowing (e.g. before the DJ presses play).
|
|
||||||
_mp3_broadcast_announced = False
|
|
||||||
|
|
||||||
# Current listener glow intensity — persisted so new/reloaded listeners get the
|
|
||||||
# correct level immediately without waiting for the DJ to move the slider again.
|
|
||||||
_current_glow_intensity = 30
|
|
||||||
|
|
||||||
# Grace-period greenlet: auto-stop broadcast if DJ doesn't reconnect in time
|
# Grace-period greenlet: auto-stop broadcast if DJ doesn't reconnect in time
|
||||||
_dj_grace_greenlet = None
|
_dj_grace_greenlet = None
|
||||||
DJ_GRACE_PERIOD_SECS = 20 # seconds to wait before auto-stopping
|
DJ_GRACE_PERIOD_SECS = 20 # seconds to wait before auto-stopping
|
||||||
|
|
@ -145,9 +136,7 @@ def _start_transcoder_if_needed(is_mp3_input=False):
|
||||||
try: _ffmpeg_in_q.get_nowait()
|
try: _ffmpeg_in_q.get_nowait()
|
||||||
except: break
|
except: break
|
||||||
|
|
||||||
# Define greenlets INSIDE so they close over THIS specific 'proc'.
|
# Define threads INSIDE so they close over THIS specific 'proc'
|
||||||
# Blocking subprocess pipe I/O is delegated to eventlet.tpool so it runs
|
|
||||||
# in a real OS thread, preventing it from stalling the eventlet hub.
|
|
||||||
def _writer(proc):
|
def _writer(proc):
|
||||||
global _transcoder_last_error
|
global _transcoder_last_error
|
||||||
print(f"[THREAD] Transcoder writer started (PID: {proc.pid})")
|
print(f"[THREAD] Transcoder writer started (PID: {proc.pid})")
|
||||||
|
|
@ -157,9 +146,8 @@ def _start_transcoder_if_needed(is_mp3_input=False):
|
||||||
if chunk is None: break
|
if chunk is None: break
|
||||||
|
|
||||||
if proc.stdin:
|
if proc.stdin:
|
||||||
# Run blocking pipe-write in a real thread via tpool
|
proc.stdin.write(chunk)
|
||||||
eventlet.tpool.execute(proc.stdin.write, chunk)
|
proc.stdin.flush()
|
||||||
eventlet.tpool.execute(proc.stdin.flush)
|
|
||||||
except queue.Empty:
|
except queue.Empty:
|
||||||
continue
|
continue
|
||||||
except (BrokenPipeError, ConnectionResetError):
|
except (BrokenPipeError, ConnectionResetError):
|
||||||
|
|
@ -170,7 +158,7 @@ def _start_transcoder_if_needed(is_mp3_input=False):
|
||||||
_transcoder_last_error = str(e)
|
_transcoder_last_error = str(e)
|
||||||
break
|
break
|
||||||
|
|
||||||
# Ensure process is killed if greenlet exits unexpectedly
|
# Ensure process is killed if thread exits unexpectedly
|
||||||
if proc.poll() is None:
|
if proc.poll() is None:
|
||||||
try: proc.terminate()
|
try: proc.terminate()
|
||||||
except: pass
|
except: pass
|
||||||
|
|
@ -181,9 +169,9 @@ def _start_transcoder_if_needed(is_mp3_input=False):
|
||||||
print(f"[THREAD] Transcoder reader started (PID: {proc.pid})")
|
print(f"[THREAD] Transcoder reader started (PID: {proc.pid})")
|
||||||
while proc.poll() is None:
|
while proc.poll() is None:
|
||||||
try:
|
try:
|
||||||
# Run blocking pipe-read in a real thread via tpool (1 KB chunks
|
# Smaller read for smoother delivery (1KB)
|
||||||
# for smooth delivery; prevents buffering delays at lower bitrates)
|
# This prevents buffering delays at lower bitrates
|
||||||
data = eventlet.tpool.execute(proc.stdout.read, 1024)
|
data = proc.stdout.read(1024)
|
||||||
if not data: break
|
if not data: break
|
||||||
_transcoder_bytes_out += len(data)
|
_transcoder_bytes_out += len(data)
|
||||||
|
|
||||||
|
|
@ -195,20 +183,20 @@ def _start_transcoder_if_needed(is_mp3_input=False):
|
||||||
try:
|
try:
|
||||||
q.put_nowait(data)
|
q.put_nowait(data)
|
||||||
except queue.Full:
|
except queue.Full:
|
||||||
# Client is too slow — skip this chunk for them
|
# Client is too slow, skip this chunk for them
|
||||||
pass
|
pass
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(f"WARNING: Transcoder reader error: {e}")
|
print(f"WARNING: Transcoder reader error: {e}")
|
||||||
_transcoder_last_error = str(e)
|
_transcoder_last_error = str(e)
|
||||||
break
|
break
|
||||||
|
|
||||||
# Ensure process is killed if greenlet exits unexpectedly
|
# Ensure process is killed if thread exits unexpectedly
|
||||||
if proc.poll() is None:
|
if proc.poll() is None:
|
||||||
try: proc.terminate()
|
try: proc.terminate()
|
||||||
except: pass
|
except: pass
|
||||||
print(f"[THREAD] Transcoder reader finished (PID: {proc.pid})")
|
print(f"[THREAD] Transcoder reader finished (PID: {proc.pid})")
|
||||||
|
|
||||||
# Spawn as greenlets — tpool handles the blocking subprocess I/O internally
|
# Start greenlets/threads for THIS process specifically
|
||||||
eventlet.spawn(_writer, _ffmpeg_proc)
|
eventlet.spawn(_writer, _ffmpeg_proc)
|
||||||
eventlet.spawn(_reader, _ffmpeg_proc)
|
eventlet.spawn(_reader, _ffmpeg_proc)
|
||||||
|
|
||||||
|
|
@ -318,10 +306,7 @@ def setup_shared_routes(app, index_file='index.html'):
|
||||||
rel_path = os.path.relpath(os.path.join(root, filename), MUSIC_FOLDER)
|
rel_path = os.path.relpath(os.path.join(root, filename), MUSIC_FOLDER)
|
||||||
library.append({
|
library.append({
|
||||||
"title": os.path.splitext(filename)[0],
|
"title": os.path.splitext(filename)[0],
|
||||||
"file": f"music_proxy/{rel_path}",
|
"file": f"music_proxy/{rel_path}"
|
||||||
# Absolute path exposed for Tauri's asset protocol (convertFileSrc).
|
|
||||||
# The web fallback keeps using the music_proxy route above.
|
|
||||||
"absolutePath": os.path.abspath(os.path.join(root, filename))
|
|
||||||
})
|
})
|
||||||
break # Top-level only
|
break # Top-level only
|
||||||
return jsonify(library)
|
return jsonify(library)
|
||||||
|
|
@ -676,14 +661,12 @@ def dj_connect():
|
||||||
|
|
||||||
def _dj_disconnect_grace():
|
def _dj_disconnect_grace():
|
||||||
"""Auto-stop broadcast if no DJ reconnects within the grace period."""
|
"""Auto-stop broadcast if no DJ reconnects within the grace period."""
|
||||||
global _dj_grace_greenlet, _mp3_broadcast_announced
|
global _dj_grace_greenlet
|
||||||
print(f"INFO: Grace period started — {DJ_GRACE_PERIOD_SECS}s for DJ to reconnect")
|
print(f"INFO: Grace period started — {DJ_GRACE_PERIOD_SECS}s for DJ to reconnect")
|
||||||
eventlet.sleep(DJ_GRACE_PERIOD_SECS)
|
eventlet.sleep(DJ_GRACE_PERIOD_SECS)
|
||||||
if broadcast_state.get('active') and not dj_sids:
|
if broadcast_state.get('active') and not dj_sids:
|
||||||
print("WARNING: Grace period expired, no DJ reconnected — auto-stopping broadcast")
|
print("WARNING: Grace period expired, no DJ reconnected — auto-stopping broadcast")
|
||||||
broadcast_state['active'] = False
|
broadcast_state['active'] = False
|
||||||
broadcast_state['is_mp3_input'] = False
|
|
||||||
_mp3_broadcast_announced = False
|
|
||||||
_stop_transcoder()
|
_stop_transcoder()
|
||||||
listener_socketio.emit('broadcast_stopped', namespace='/')
|
listener_socketio.emit('broadcast_stopped', namespace='/')
|
||||||
listener_socketio.emit('stream_status', {'active': False}, namespace='/')
|
listener_socketio.emit('stream_status', {'active': False}, namespace='/')
|
||||||
|
|
@ -708,11 +691,11 @@ def dj_disconnect():
|
||||||
|
|
||||||
@dj_socketio.on('start_broadcast')
|
@dj_socketio.on('start_broadcast')
|
||||||
def dj_start(data=None):
|
def dj_start(data=None):
|
||||||
global _dj_grace_greenlet, _mp3_broadcast_announced
|
global _dj_grace_greenlet
|
||||||
|
|
||||||
# Cancel any pending auto-stop grace period (DJ reconnected in time)
|
# Cancel any pending auto-stop grace period (DJ reconnected in time)
|
||||||
if _dj_grace_greenlet is not None:
|
if _dj_grace_greenlet is not None:
|
||||||
_dj_grace_greenlet.kill(block=True) # block=True prevents a GreenletExit race
|
_dj_grace_greenlet.kill()
|
||||||
_dj_grace_greenlet = None
|
_dj_grace_greenlet = None
|
||||||
print("INFO: DJ reconnected within grace period — broadcast continues")
|
print("INFO: DJ reconnected within grace period — broadcast continues")
|
||||||
|
|
||||||
|
|
@ -732,23 +715,15 @@ def dj_start(data=None):
|
||||||
broadcast_state['is_mp3_input'] = is_mp3_input
|
broadcast_state['is_mp3_input'] = is_mp3_input
|
||||||
|
|
||||||
if not was_already_active:
|
if not was_already_active:
|
||||||
# Fresh broadcast start — clear pre-roll and reset announcement flag.
|
# Fresh broadcast start — clear pre-roll.
|
||||||
|
# For non-MP3 input start the ffmpeg transcoder; for MP3 input chunks are
|
||||||
|
# distributed directly via _distribute_mp3(), no transcoder required.
|
||||||
with _mp3_lock:
|
with _mp3_lock:
|
||||||
_mp3_preroll.clear()
|
_mp3_preroll.clear()
|
||||||
|
if not is_mp3_input:
|
||||||
if is_mp3_input:
|
|
||||||
# MP3-direct mode (Qt client): the DJ still needs to press play before
|
|
||||||
# audio flows. Firing broadcast_started now would cause listeners to
|
|
||||||
# connect to /stream.mp3 before any data exists, wait 60 s, then
|
|
||||||
# disconnect. Instead we hold back the announcement and send it on
|
|
||||||
# the very first audio_chunk so listeners connect when data is ready.
|
|
||||||
_mp3_broadcast_announced = False
|
|
||||||
print("BROADCAST: MP3-direct mode — deferring broadcast_started until first chunk")
|
|
||||||
else:
|
|
||||||
# Non-MP3 (browser webm/opus): ffmpeg transcoder starts immediately,
|
|
||||||
# so listeners can connect right away.
|
|
||||||
_start_transcoder_if_needed(is_mp3_input=False)
|
_start_transcoder_if_needed(is_mp3_input=False)
|
||||||
listener_socketio.emit('broadcast_started', namespace='/')
|
# Tell listeners a new broadcast has begun (triggers audio player reload)
|
||||||
|
listener_socketio.emit('broadcast_started', namespace='/')
|
||||||
else:
|
else:
|
||||||
# DJ reconnected mid-broadcast - just ensure transcoder is alive (non-MP3 only)
|
# DJ reconnected mid-broadcast - just ensure transcoder is alive (non-MP3 only)
|
||||||
# Do NOT clear pre-roll or trigger listener reload
|
# Do NOT clear pre-roll or trigger listener reload
|
||||||
|
|
@ -756,12 +731,8 @@ def dj_start(data=None):
|
||||||
if not is_mp3_input:
|
if not is_mp3_input:
|
||||||
_start_transcoder_if_needed(is_mp3_input=False)
|
_start_transcoder_if_needed(is_mp3_input=False)
|
||||||
|
|
||||||
# For MP3-direct fresh broadcast, hold back stream_status active=true too
|
# Always send current status so any waiting listeners get unblocked
|
||||||
# so listeners don't auto-connect before audio data is flowing.
|
listener_socketio.emit('stream_status', {'active': True}, namespace='/')
|
||||||
# It will be sent alongside broadcast_started on the first audio_chunk.
|
|
||||||
# For non-MP3 (browser) mode or DJ reconnects, send immediately.
|
|
||||||
if not (is_mp3_input and not was_already_active):
|
|
||||||
listener_socketio.emit('stream_status', {'active': True}, namespace='/')
|
|
||||||
|
|
||||||
@dj_socketio.on('get_listener_count')
|
@dj_socketio.on('get_listener_count')
|
||||||
def dj_get_listener_count():
|
def dj_get_listener_count():
|
||||||
|
|
@ -770,10 +741,8 @@ def dj_get_listener_count():
|
||||||
@dj_socketio.on('listener_glow')
|
@dj_socketio.on('listener_glow')
|
||||||
def dj_listener_glow(data):
|
def dj_listener_glow(data):
|
||||||
"""DJ sets the glow intensity on the listener page."""
|
"""DJ sets the glow intensity on the listener page."""
|
||||||
global _current_glow_intensity
|
|
||||||
intensity = int(data.get('intensity', 30)) if isinstance(data, dict) else 30
|
intensity = int(data.get('intensity', 30)) if isinstance(data, dict) else 30
|
||||||
intensity = max(0, min(100, intensity))
|
intensity = max(0, min(100, intensity))
|
||||||
_current_glow_intensity = intensity
|
|
||||||
listener_socketio.emit('listener_glow', {'intensity': intensity}, namespace='/')
|
listener_socketio.emit('listener_glow', {'intensity': intensity}, namespace='/')
|
||||||
|
|
||||||
@dj_socketio.on('deck_glow')
|
@dj_socketio.on('deck_glow')
|
||||||
|
|
@ -786,22 +755,9 @@ def dj_deck_glow(data):
|
||||||
'B': bool(data.get('B', False)),
|
'B': bool(data.get('B', False)),
|
||||||
}, namespace='/')
|
}, namespace='/')
|
||||||
|
|
||||||
@dj_socketio.on('now_playing')
|
|
||||||
def dj_now_playing(data):
|
|
||||||
"""Relay the currently playing track title to all listener pages."""
|
|
||||||
if not isinstance(data, dict):
|
|
||||||
return
|
|
||||||
listener_socketio.emit('now_playing', {
|
|
||||||
'title': str(data.get('title', '')),
|
|
||||||
'deck': str(data.get('deck', '')),
|
|
||||||
}, namespace='/')
|
|
||||||
|
|
||||||
@dj_socketio.on('stop_broadcast')
|
@dj_socketio.on('stop_broadcast')
|
||||||
def dj_stop():
|
def dj_stop():
|
||||||
global _mp3_broadcast_announced
|
|
||||||
broadcast_state['active'] = False
|
broadcast_state['active'] = False
|
||||||
broadcast_state['is_mp3_input'] = False
|
|
||||||
_mp3_broadcast_announced = False
|
|
||||||
session['is_dj'] = False
|
session['is_dj'] = False
|
||||||
print("STOPPED: DJ stopped broadcasting")
|
print("STOPPED: DJ stopped broadcasting")
|
||||||
|
|
||||||
|
|
@ -812,17 +768,9 @@ def dj_stop():
|
||||||
|
|
||||||
@dj_socketio.on('audio_chunk')
|
@dj_socketio.on('audio_chunk')
|
||||||
def dj_audio(data):
|
def dj_audio(data):
|
||||||
global _mp3_broadcast_announced
|
|
||||||
if broadcast_state['active'] and isinstance(data, (bytes, bytearray)):
|
if broadcast_state['active'] and isinstance(data, (bytes, bytearray)):
|
||||||
if broadcast_state.get('is_mp3_input', False):
|
if broadcast_state.get('is_mp3_input', False):
|
||||||
# MP3 input (e.g. Qt client): skip ffmpeg, send directly to listeners.
|
# MP3 input (e.g. Qt client): skip ffmpeg, send directly to listeners
|
||||||
# Fire broadcast_started on the very first chunk so listeners connect
|
|
||||||
# only once actual audio data is flowing (the DJ has pressed play).
|
|
||||||
if not _mp3_broadcast_announced:
|
|
||||||
_mp3_broadcast_announced = True
|
|
||||||
print("BROADCAST: First MP3 chunk received — announcing broadcast_started to listeners")
|
|
||||||
listener_socketio.emit('broadcast_started', namespace='/')
|
|
||||||
listener_socketio.emit('stream_status', {'active': True}, namespace='/')
|
|
||||||
_distribute_mp3(bytes(data))
|
_distribute_mp3(bytes(data))
|
||||||
else:
|
else:
|
||||||
# Other formats (e.g. webm/opus from browser): route through ffmpeg transcoder
|
# Other formats (e.g. webm/opus from browser): route through ffmpeg transcoder
|
||||||
|
|
@ -897,9 +845,8 @@ def listener_disconnect():
|
||||||
|
|
||||||
@listener_socketio.on('join_listener')
|
@listener_socketio.on('join_listener')
|
||||||
def listener_join():
|
def listener_join():
|
||||||
# SID already added in listener_connect(); send stream status and current glow
|
# SID already added in listener_connect(); just send stream status back
|
||||||
emit('stream_status', {'active': broadcast_state['active']})
|
emit('stream_status', {'active': broadcast_state['active']})
|
||||||
emit('listener_glow', {'intensity': _current_glow_intensity})
|
|
||||||
|
|
||||||
@listener_socketio.on('get_listener_count')
|
@listener_socketio.on('get_listener_count')
|
||||||
def listener_get_count():
|
def listener_get_count():
|
||||||
|
|
|
||||||
|
|
@ -1,21 +0,0 @@
|
||||||
[package]
|
|
||||||
name = "techdj"
|
|
||||||
version = "0.1.0"
|
|
||||||
edition = "2021"
|
|
||||||
|
|
||||||
[build-dependencies]
|
|
||||||
tauri-build = { version = "2", features = [] }
|
|
||||||
|
|
||||||
[dependencies]
|
|
||||||
tauri = { version = "2", features = [] }
|
|
||||||
tauri-plugin-fs = "2"
|
|
||||||
serde = { version = "1", features = ["derive"] }
|
|
||||||
serde_json = "1"
|
|
||||||
|
|
||||||
# Release optimisations — keep the binary small on the 4 GB machine
|
|
||||||
[profile.release]
|
|
||||||
panic = "abort"
|
|
||||||
codegen-units = 1
|
|
||||||
lto = true
|
|
||||||
opt-level = "s"
|
|
||||||
strip = true
|
|
||||||
|
|
@ -1,3 +0,0 @@
|
||||||
fn main() {
|
|
||||||
tauri_build::build()
|
|
||||||
}
|
|
||||||
|
|
@ -1,11 +0,0 @@
|
||||||
{
|
|
||||||
"$schema": "https://schema.tauri.app/config/2/acl/capability.json",
|
|
||||||
"identifier": "default",
|
|
||||||
"description": "TechDJ default permissions — read-only access to home directory for local audio",
|
|
||||||
"windows": ["main"],
|
|
||||||
"permissions": [
|
|
||||||
"core:default",
|
|
||||||
"fs:read-all",
|
|
||||||
"fs:scope-home-recursive"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
|
|
@ -1,10 +0,0 @@
|
||||||
#[cfg_attr(mobile, tauri::mobile_entry_point)]
|
|
||||||
pub fn run() {
|
|
||||||
tauri::Builder::default()
|
|
||||||
// Grant the WebView direct read access to local audio files so
|
|
||||||
// convertFileSrc() can serve tracks from $HOME without going
|
|
||||||
// through the Flask proxy.
|
|
||||||
.plugin(tauri_plugin_fs::init())
|
|
||||||
.run(tauri::generate_context!())
|
|
||||||
.expect("error while running TechDJ");
|
|
||||||
}
|
|
||||||
|
|
@ -1,6 +0,0 @@
|
||||||
// Hides the console window on Windows release builds; harmless on Linux.
|
|
||||||
#![cfg_attr(not(debug_assertions), windows_subsystem = "windows")]
|
|
||||||
|
|
||||||
fn main() {
|
|
||||||
techdj_lib::run()
|
|
||||||
}
|
|
||||||
|
|
@ -1,38 +0,0 @@
|
||||||
{
|
|
||||||
"$schema": "https://schema.tauri.app/config/2",
|
|
||||||
"productName": "TechDJ",
|
|
||||||
"version": "0.1.0",
|
|
||||||
"identifier": "dev.computertech.techdj",
|
|
||||||
"build": {
|
|
||||||
"frontendDist": "../"
|
|
||||||
},
|
|
||||||
"app": {
|
|
||||||
"withGlobalTauri": true,
|
|
||||||
"windows": [
|
|
||||||
{
|
|
||||||
"title": "TechDJ",
|
|
||||||
"width": 1280,
|
|
||||||
"height": 800,
|
|
||||||
"minWidth": 1024,
|
|
||||||
"minHeight": 600,
|
|
||||||
"decorations": true,
|
|
||||||
"fullscreen": false,
|
|
||||||
"resizable": true
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"security": {
|
|
||||||
"assetProtocol": {
|
|
||||||
"enable": true,
|
|
||||||
"scope": [
|
|
||||||
"$HOME/**",
|
|
||||||
"$HOME/Music/**"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"bundle": {
|
|
||||||
"active": true,
|
|
||||||
"targets": ["deb"],
|
|
||||||
"icon": ["../icon.png"]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Loading…
Reference in New Issue