diff --git a/.gitea/workflows/build-push.yaml b/.gitea/workflows/build-push.yaml index 340fb686..cee2ff8b 100644 --- a/.gitea/workflows/build-push.yaml +++ b/.gitea/workflows/build-push.yaml @@ -3,8 +3,7 @@ name: Build and Push Docker Image on: push: branches: - - main # Changez cela si votre branche principale a un autre nom - + - main # Changez cela si votre branche principale a un autre nom jobs: build: @@ -15,7 +14,13 @@ jobs: uses: actions/cache@v3 with: path: ~/.npm - key: dont-cache-${{ github.run_id }} + key: dont-cache-${{ github.run_id }} + + - name: Extract version from Cargo.toml + run: | + grep '^version = ' PMOMusic/Cargo.toml | head -n 1 | sed 's/version = "\(.*\)"/\1/' > version.txt + echo "Version extracted: $(cat version.txt)" + - name: Build and push image uses: https://gargoton.petite-maison-orange.fr/pmo-actions/build-push-image@main with: @@ -24,6 +29,3 @@ jobs: no_cache: true version_file: version.txt check_uuid: 82a30d23-b3bd-4199-9237-776965831d20 - - - \ No newline at end of file diff --git a/Cargo.lock b/Cargo.lock index 363d1caf..54552d0d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4,7 +4,7 @@ version = 4 [[package]] name = "PMOMusic" -version = "0.1.0" +version = "0.3.1" dependencies = [ "axum 0.8.7", "console-subscriber", diff --git a/Makefile b/Makefile index ef3912cb..d264446c 100644 --- a/Makefile +++ b/Makefile @@ -13,6 +13,7 @@ BINARY_NAME = PMOMusic # Couleurs pour l'affichage GREEN = \033[0;32m YELLOW = \033[1;33m +BLUE = \033[1;34m RED = \033[0;31m NC = \033[0m # No Color @@ -193,6 +194,21 @@ update: cd $(WEBAPP_DIR) && $(NPM) update @echo "$(GREEN)✓ Dépendances mises à jour$(NC)" +## bump-version: Incrémente le numéro de version patch (x.y.z -> x.y.z+1) +bump-version: + @echo "$(YELLOW)→ Incrémentation de la version...$(NC)" + @current=$$(grep '^version = ' PMOMusic/Cargo.toml | head -n 1 | sed 's/version = "\(.*\)"/\1/'); \ + echo " Version actuelle: $$current"; \ + major=$$(echo $$current | cut -d. -f1); \ + minor=$$(echo $$current | cut -d. -f2); \ + patch=$$(echo $$current | cut -d. -f3); \ + new_patch=$$((patch + 1)); \ + new_version="$$major.$$minor.$$new_patch"; \ + echo " Nouvelle version: $$new_version"; \ + sed -i.bak "s/^version = \"$$current\"/version = \"$$new_version\"/" PMOMusic/Cargo.toml && \ + rm PMOMusic/Cargo.toml.bak + @echo "$(GREEN)✓ Version mise à jour dans PMOMusic/Cargo.toml$(NC)" + ## bench: Exécute les benchmarks bench: @echo "$(YELLOW)→ Exécution des benchmarks...$(NC)" @@ -203,4 +219,23 @@ coverage: @echo "$(YELLOW)→ Génération du rapport de couverture...$(NC)" $(CARGO) tarpaulin --out Html --output-dir target/coverage @echo "$(GREEN)✓ Rapport disponible dans target/coverage/index.html$(NC)" - + +jjnew: + @echo "$(YELLOW)→ Création d'un nouveau commit...$(NC)" + @echo "$(BLUE)→ Documentation du commit courrant...$(NC)" + @jj auto-describe + @echo "$(BLUE)→ C'est fait.$(NC)" + @jj new + @echo "$(GREEN)✓ nouveau commit créé$(NC)" + +jjpush: bump-version + @echo "$(YELLOW)→ Push du commit sur le dépôt...$(NC)" + @jj auto-describe + @jj git push --change @ + @echo "$(GREEN)✓ Commit pushé sur le dépôt$(NC)" + +jjfetch: + @echo "$(YELLOW)→ Pull des derniers commits...$(NC)" + @jj git fetch + @jj new main@origin + @echo "$(GREEN)✓ Derniers commits pullés$(NC)" diff --git a/PMOMusic/Cargo.toml b/PMOMusic/Cargo.toml index 2955dd5c..3853aa5c 100644 --- a/PMOMusic/Cargo.toml +++ b/PMOMusic/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "PMOMusic" -version = "0.1.0" +version = "0.3.1" edition = "2024" [dependencies] diff --git a/pmoapp/webapp/src/components/pmocontrol/MediaBrowser.vue b/pmoapp/webapp/src/components/pmocontrol/MediaBrowser.vue index 10af54c8..5a8dfd85 100644 --- a/pmoapp/webapp/src/components/pmocontrol/MediaBrowser.vue +++ b/pmoapp/webapp/src/components/pmocontrol/MediaBrowser.vue @@ -1,62 +1,58 @@ diff --git a/pmocontrol/src/control_point.rs b/pmocontrol/src/control_point.rs index 4b2d89e7..560a6628 100644 --- a/pmocontrol/src/control_point.rs +++ b/pmocontrol/src/control_point.rs @@ -112,8 +112,8 @@ impl ControlPoint { ]; loop { - // Attendre 60 secondes avant le prochain cycle - thread::sleep(Duration::from_secs(60)); + // Attendre 10 secondes avant le prochain cycle pour découverte rapide + thread::sleep(Duration::from_secs(10)); debug!("Sending periodic M-SEARCH for device discovery"); @@ -303,9 +303,9 @@ impl ControlPoint { new_snapshot.state = Some(logical_state); } - // Poll volume and mute less frequently (every 3 seconds) - // to reduce SOAP overhead without impacting UI responsiveness - if tick % 3 == 0 { + // Poll volume and mute every second (every 2 ticks at 500ms) + // for responsive volume control feedback + if tick % 2 == 0 { if let Ok(volume) = renderer.volume() { if prev_snapshot.last_volume != Some(volume) { polling_cp.emit_renderer_event(RendererEvent::VolumeChanged { @@ -334,8 +334,8 @@ impl ControlPoint { } tick = tick.wrapping_add(1); - // Keep 1 second polling for smooth position updates - thread::sleep(Duration::from_secs(1)); + // 500ms polling for smoother position updates and progress bar + thread::sleep(Duration::from_millis(500)); } }); diff --git a/pmocontrol/src/pmoserver_ext.rs b/pmocontrol/src/pmoserver_ext.rs index c12a2f91..8da92766 100644 --- a/pmocontrol/src/pmoserver_ext.rs +++ b/pmocontrol/src/pmoserver_ext.rs @@ -1497,87 +1497,79 @@ async fn play_content( })?; let control_point = Arc::clone(&state.control_point); + let rid_for_log = rid.clone(); + let object_id_for_log = object_id.clone(); + let object_id_for_debug = object_id_for_log.clone(); - // Spawn blocking task for content loading - let play_task = tokio::task::spawn_blocking(move || { - // Fetch playback items from server - let items = fetch_playback_items(&control_point, &sid, &object_id)?; + // Launch the command in background and return immediately + // The UI will be updated via SSE events when playback starts + tokio::task::spawn(async move { + let result = tokio::task::spawn_blocking(move || { + // Fetch playback items from server + let items = fetch_playback_items(&control_point, &sid, &object_id)?; - if items.is_empty() { - return Err(anyhow::anyhow!("No playable content found")); + if items.is_empty() { + return Err(anyhow::anyhow!("No playable content found")); + } + + if items.len() > 1 { + debug!( + renderer = rid.0.as_str(), + server = sid.0.as_str(), + object = object_id.as_str(), + item_count = items.len(), + "Auto-binding playlist to renderer queue (auto_play = true)" + ); + control_point.attach_queue_to_playlist_with_options( + &rid, + sid.clone(), + object_id.clone(), + true, + )?; + return Ok(()); + } + + // Clear queue + control_point.clear_queue(&rid)?; + + // Enqueue items + control_point.enqueue_items(&rid, items)?; + + // Start playback + // Pour les renderers OpenHome, play_current_from_queue() va gérer automatiquement + // la lecture depuis la playlist native si elle existe + control_point.play_current_from_queue(&rid)?; + + Ok::<(), anyhow::Error>(()) + }) + .await; + + match result { + Ok(Ok(())) => { + debug!( + "Successfully started playing content {} on renderer {}", + object_id_for_log, rid_for_log.0 + ); + } + Ok(Err(e)) => { + warn!( + "Failed to play content on renderer {}: {}", + rid_for_log.0, e + ); + } + Err(e) => { + warn!( + "Task join error during play content for renderer {}: {}", + rid_for_log.0, e + ); + } } - - if items.len() > 1 { - debug!( - renderer = rid.0.as_str(), - server = sid.0.as_str(), - object = object_id.as_str(), - item_count = items.len(), - "Auto-binding playlist to renderer queue (auto_play = true)" - ); - control_point.attach_queue_to_playlist_with_options( - &rid, - sid.clone(), - object_id.clone(), - true, - )?; - return Ok(()); - } - - // Clear queue - control_point.clear_queue(&rid)?; - - // Enqueue items - control_point.enqueue_items(&rid, items)?; - - // Start playback - // Pour les renderers OpenHome, play_current_from_queue() va gérer automatiquement - // la lecture depuis la playlist native si elle existe - control_point.play_current_from_queue(&rid)?; - - Ok::<(), anyhow::Error>(()) }); - time::timeout(QUEUE_COMMAND_TIMEOUT, play_task) - .await - .map_err(|_| { - warn!( - "Play content command for renderer {} exceeded {:?}", - renderer_id, QUEUE_COMMAND_TIMEOUT - ); - ( - StatusCode::GATEWAY_TIMEOUT, - Json(ErrorResponse { - error: format!( - "Play content timed out after {}s", - QUEUE_COMMAND_TIMEOUT.as_secs() - ), - }), - ) - })? - .map_err(|e| { - warn!("Task join error during play content: {}", e); - ( - StatusCode::INTERNAL_SERVER_ERROR, - Json(ErrorResponse { - error: format!("Internal task error: {}", e), - }), - ) - })? - .map_err(|e| { - warn!("Failed to play content on renderer {}: {}", renderer_id, e); - ( - StatusCode::INTERNAL_SERVER_ERROR, - Json(ErrorResponse { - error: format!("Failed to play content: {}", e), - }), - ) - })?; - debug!( renderer = renderer_id.as_str(), server = req.server_id.as_str(), - object = object_id_for_log.as_str(), + object = object_id_for_debug.as_str(), "Content playing via HTTP API" ); @@ -1626,65 +1618,54 @@ async fn add_to_queue( })?; let control_point = Arc::clone(&state.control_point); + let rid_for_log = rid.clone(); + let object_id_for_log = object_id.clone(); + let object_id_for_debug = object_id_for_log.clone(); - // Spawn blocking task for content loading - let add_task = tokio::task::spawn_blocking(move || { - // Fetch playback items from server - let items = fetch_playback_items(&control_point, &sid, &object_id)?; + // Launch the command in background and return immediately + // The UI will be updated via SSE events when the queue changes + tokio::task::spawn(async move { + let result = tokio::task::spawn_blocking(move || { + // Fetch playback items from server + let items = fetch_playback_items(&control_point, &sid, &object_id)?; - if items.is_empty() { - return Err(anyhow::anyhow!("No playable content found")); + if items.is_empty() { + return Err(anyhow::anyhow!("No playable content found")); + } + + // Enqueue items + control_point.enqueue_items(&rid, items)?; + + Ok::<(), anyhow::Error>(()) + }) + .await; + + match result { + Ok(Ok(())) => { + debug!( + "Successfully added content {} to queue for renderer {}", + object_id_for_log, rid_for_log.0 + ); + } + Ok(Err(e)) => { + warn!( + "Failed to add content to queue for renderer {}: {}", + rid_for_log.0, e + ); + } + Err(e) => { + warn!( + "Task join error during add to queue for renderer {}: {}", + rid_for_log.0, e + ); + } } - - // Enqueue items - control_point.enqueue_items(&rid, items)?; - - Ok::<(), anyhow::Error>(()) }); - time::timeout(QUEUE_COMMAND_TIMEOUT, add_task) - .await - .map_err(|_| { - warn!( - "Add to queue command for renderer {} exceeded {:?}", - renderer_id, QUEUE_COMMAND_TIMEOUT - ); - ( - StatusCode::GATEWAY_TIMEOUT, - Json(ErrorResponse { - error: format!( - "Add to queue timed out after {}s", - QUEUE_COMMAND_TIMEOUT.as_secs() - ), - }), - ) - })? - .map_err(|e| { - warn!("Task join error during add to queue: {}", e); - ( - StatusCode::INTERNAL_SERVER_ERROR, - Json(ErrorResponse { - error: format!("Internal task error: {}", e), - }), - ) - })? - .map_err(|e| { - warn!( - "Failed to add content to queue for renderer {}: {}", - renderer_id, e - ); - ( - StatusCode::INTERNAL_SERVER_ERROR, - Json(ErrorResponse { - error: format!("Failed to add to queue: {}", e), - }), - ) - })?; - debug!( renderer = renderer_id.as_str(), server = req.server_id.as_str(), - object = object_id_for_log.as_str(), + object = object_id_for_debug.as_str(), "Content added to queue via HTTP API" ); @@ -1734,69 +1715,58 @@ async fn add_after_current( })?; let control_point = Arc::clone(&state.control_point); + let rid_for_log = rid.clone(); + let object_id_for_log = object_id.clone(); + let object_id_for_debug = object_id_for_log.clone(); - // Spawn blocking task for content loading - let add_task = tokio::task::spawn_blocking(move || { - // Fetch playback items from server - let items = fetch_playback_items(&control_point, &sid, &object_id)?; + // Launch the command in background and return immediately + // The UI will be updated via SSE events when the queue changes + tokio::task::spawn(async move { + let result = tokio::task::spawn_blocking(move || { + // Fetch playback items from server + let items = fetch_playback_items(&control_point, &sid, &object_id)?; - if items.is_empty() { - return Err(anyhow::anyhow!("No playable content found")); + if items.is_empty() { + return Err(anyhow::anyhow!("No playable content found")); + } + + // Insert items after current using the new method + control_point.enqueue_items_with_mode( + &rid, + items, + crate::queue::EnqueueMode::InsertAfterCurrent, + )?; + + Ok::<(), anyhow::Error>(()) + }) + .await; + + match result { + Ok(Ok(())) => { + debug!( + "Successfully added content {} after current for renderer {}", + object_id_for_log, rid_for_log.0 + ); + } + Ok(Err(e)) => { + warn!( + "Failed to add content after current for renderer {}: {}", + rid_for_log.0, e + ); + } + Err(e) => { + warn!( + "Task join error during add after current for renderer {}: {}", + rid_for_log.0, e + ); + } } - - // Insert items after current using the new method - control_point.enqueue_items_with_mode( - &rid, - items, - crate::queue::EnqueueMode::InsertAfterCurrent, - )?; - - Ok::<(), anyhow::Error>(()) }); - time::timeout(QUEUE_COMMAND_TIMEOUT, add_task) - .await - .map_err(|_| { - warn!( - "Add after current command for renderer {} exceeded {:?}", - renderer_id, QUEUE_COMMAND_TIMEOUT - ); - ( - StatusCode::GATEWAY_TIMEOUT, - Json(ErrorResponse { - error: format!( - "Add after current timed out after {}s", - QUEUE_COMMAND_TIMEOUT.as_secs() - ), - }), - ) - })? - .map_err(|e| { - warn!("Task join error during add after current: {}", e); - ( - StatusCode::INTERNAL_SERVER_ERROR, - Json(ErrorResponse { - error: format!("Internal task error: {}", e), - }), - ) - })? - .map_err(|e| { - warn!( - "Failed to add content after current for renderer {}: {}", - renderer_id, e - ); - ( - StatusCode::INTERNAL_SERVER_ERROR, - Json(ErrorResponse { - error: format!("Failed to add after current: {}", e), - }), - ) - })?; - debug!( renderer = renderer_id.as_str(), server = req.server_id.as_str(), - object = object_id_for_log.as_str(), + object = object_id_for_debug.as_str(), "Content added after current via HTTP API" ); diff --git a/version.txt b/version.txt deleted file mode 100644 index 0d91a54c..00000000 --- a/version.txt +++ /dev/null @@ -1 +0,0 @@ -0.3.0