|
|
|
@ -66,6 +66,10 @@ |
|
|
|
<input type="number" step="0.1" min="0" name="fade_out_seconds" id="fade_out_seconds" value="0" /> |
|
|
|
<input type="number" step="0.1" min="0" name="fade_out_seconds" id="fade_out_seconds" value="0" /> |
|
|
|
</label> |
|
|
|
</label> |
|
|
|
</div> |
|
|
|
</div> |
|
|
|
|
|
|
|
<label> |
|
|
|
|
|
|
|
Nombre de répétitions |
|
|
|
|
|
|
|
<input type="number" step="1" min="0" name="repeat_count" id="repeat_count" value="0" /> |
|
|
|
|
|
|
|
</label> |
|
|
|
<label class="normalize-row"> |
|
|
|
<label class="normalize-row"> |
|
|
|
<input type="checkbox" name="normalize_audio" id="normalize_audio" /> |
|
|
|
<input type="checkbox" name="normalize_audio" id="normalize_audio" /> |
|
|
|
Normaliser le niveau sonore |
|
|
|
Normaliser le niveau sonore |
|
|
|
@ -93,6 +97,7 @@ |
|
|
|
<th>Volume</th> |
|
|
|
<th>Volume</th> |
|
|
|
<th>Fade in</th> |
|
|
|
<th>Fade in</th> |
|
|
|
<th>Fade out</th> |
|
|
|
<th>Fade out</th> |
|
|
|
|
|
|
|
<th>Répétitions</th> |
|
|
|
<th>Normalisation</th> |
|
|
|
<th>Normalisation</th> |
|
|
|
<th>Actions</th> |
|
|
|
<th>Actions</th> |
|
|
|
</tr> |
|
|
|
</tr> |
|
|
|
@ -110,6 +115,7 @@ |
|
|
|
data-trigger-volume="{{ trigger.get('volume', 80) }}" |
|
|
|
data-trigger-volume="{{ trigger.get('volume', 80) }}" |
|
|
|
data-trigger-fade-in="{{ trigger.get('fade_in_seconds', 0.0) }}" |
|
|
|
data-trigger-fade-in="{{ trigger.get('fade_in_seconds', 0.0) }}" |
|
|
|
data-trigger-fade-out="{{ trigger.get('fade_out_seconds', 0.0) }}" |
|
|
|
data-trigger-fade-out="{{ trigger.get('fade_out_seconds', 0.0) }}" |
|
|
|
|
|
|
|
data-trigger-repeat-count="{{ trigger.get('repeat_count', 0) }}" |
|
|
|
data-trigger-normalize="{{ 1 if trigger.get('normalize_audio', false) else 0 }}" |
|
|
|
data-trigger-normalize="{{ 1 if trigger.get('normalize_audio', false) else 0 }}" |
|
|
|
title="Cliquer pour charger ce trigger dans le formulaire" |
|
|
|
title="Cliquer pour charger ce trigger dans le formulaire" |
|
|
|
> |
|
|
|
> |
|
|
|
@ -121,12 +127,14 @@ |
|
|
|
<td>{{ trigger.get('volume', 80) }}</td> |
|
|
|
<td>{{ trigger.get('volume', 80) }}</td> |
|
|
|
<td>{{ trigger.get('fade_in_seconds', 0.0) }}</td> |
|
|
|
<td>{{ trigger.get('fade_in_seconds', 0.0) }}</td> |
|
|
|
<td>{{ trigger.get('fade_out_seconds', 0.0) }}</td> |
|
|
|
<td>{{ trigger.get('fade_out_seconds', 0.0) }}</td> |
|
|
|
|
|
|
|
<td>{{ trigger.get('repeat_count', 0) }}</td> |
|
|
|
<td>{{ 'Oui' if trigger.get('normalize_audio', false) else 'Non' }}</td> |
|
|
|
<td>{{ 'Oui' if trigger.get('normalize_audio', false) else 'Non' }}</td> |
|
|
|
<td> |
|
|
|
<td> |
|
|
|
<button |
|
|
|
<button |
|
|
|
type="button" |
|
|
|
type="button" |
|
|
|
class="small js-play-trigger" |
|
|
|
class="small js-play-trigger" |
|
|
|
data-trigger-id="{{ trigger_id }}" |
|
|
|
data-trigger-id="{{ trigger_id }}" |
|
|
|
|
|
|
|
data-repeat-count="{{ trigger.get('repeat_count', 0) }}" |
|
|
|
data-play-url="{{ url_for('ui.play_trigger') }}" |
|
|
|
data-play-url="{{ url_for('ui.play_trigger') }}" |
|
|
|
>Lancer</button> |
|
|
|
>Lancer</button> |
|
|
|
<button |
|
|
|
<button |
|
|
|
@ -315,6 +323,7 @@ |
|
|
|
const endVal = (t.end_seconds != null && t.end_seconds !== "") ? t.end_seconds : ""; |
|
|
|
const endVal = (t.end_seconds != null && t.end_seconds !== "") ? t.end_seconds : ""; |
|
|
|
const fadeInVal = t.fade_in_seconds != null ? t.fade_in_seconds : 0; |
|
|
|
const fadeInVal = t.fade_in_seconds != null ? t.fade_in_seconds : 0; |
|
|
|
const fadeOutVal = t.fade_out_seconds != null ? t.fade_out_seconds : 0; |
|
|
|
const fadeOutVal = t.fade_out_seconds != null ? t.fade_out_seconds : 0; |
|
|
|
|
|
|
|
const repeatCountVal = t.repeat_count != null ? t.repeat_count : 0; |
|
|
|
const normalizeVal = !!t.normalize_audio; |
|
|
|
const normalizeVal = !!t.normalize_audio; |
|
|
|
|
|
|
|
|
|
|
|
let row = tbody.querySelector(`tr[data-trigger-id="${CSS.escape(triggerId)}"]`); |
|
|
|
let row = tbody.querySelector(`tr[data-trigger-id="${CSS.escape(triggerId)}"]`); |
|
|
|
@ -338,6 +347,7 @@ |
|
|
|
set("trigger_volume", row.dataset.triggerVolume || "80"); |
|
|
|
set("trigger_volume", row.dataset.triggerVolume || "80"); |
|
|
|
set("fade_in_seconds", row.dataset.triggerFadeIn || "0"); |
|
|
|
set("fade_in_seconds", row.dataset.triggerFadeIn || "0"); |
|
|
|
set("fade_out_seconds", row.dataset.triggerFadeOut || "0"); |
|
|
|
set("fade_out_seconds", row.dataset.triggerFadeOut || "0"); |
|
|
|
|
|
|
|
set("repeat_count", row.dataset.triggerRepeatCount || "0"); |
|
|
|
const normalize = document.getElementById("normalize_audio"); |
|
|
|
const normalize = document.getElementById("normalize_audio"); |
|
|
|
if (normalize) normalize.checked = (row.dataset.triggerNormalize === "1"); |
|
|
|
if (normalize) normalize.checked = (row.dataset.triggerNormalize === "1"); |
|
|
|
document.getElementById("trigger_type")?.focus(); |
|
|
|
document.getElementById("trigger_type")?.focus(); |
|
|
|
@ -354,6 +364,7 @@ |
|
|
|
row.dataset.triggerVolume = t.volume != null ? t.volume : 80; |
|
|
|
row.dataset.triggerVolume = t.volume != null ? t.volume : 80; |
|
|
|
row.dataset.triggerFadeIn = fadeInVal; |
|
|
|
row.dataset.triggerFadeIn = fadeInVal; |
|
|
|
row.dataset.triggerFadeOut = fadeOutVal; |
|
|
|
row.dataset.triggerFadeOut = fadeOutVal; |
|
|
|
|
|
|
|
row.dataset.triggerRepeatCount = repeatCountVal; |
|
|
|
row.dataset.triggerNormalize = normalizeVal ? "1" : "0"; |
|
|
|
row.dataset.triggerNormalize = normalizeVal ? "1" : "0"; |
|
|
|
|
|
|
|
|
|
|
|
row.innerHTML = ` |
|
|
|
row.innerHTML = ` |
|
|
|
@ -365,10 +376,12 @@ |
|
|
|
<td>${t.volume != null ? t.volume : 80}</td> |
|
|
|
<td>${t.volume != null ? t.volume : 80}</td> |
|
|
|
<td>${fadeInVal}</td> |
|
|
|
<td>${fadeInVal}</td> |
|
|
|
<td>${fadeOutVal}</td> |
|
|
|
<td>${fadeOutVal}</td> |
|
|
|
|
|
|
|
<td>${repeatCountVal}</td> |
|
|
|
<td>${normalizeVal ? "Oui" : "Non"}</td> |
|
|
|
<td>${normalizeVal ? "Oui" : "Non"}</td> |
|
|
|
<td> |
|
|
|
<td> |
|
|
|
<button type="button" class="small js-play-trigger" |
|
|
|
<button type="button" class="small js-play-trigger" |
|
|
|
data-trigger-id="${triggerId}" |
|
|
|
data-trigger-id="${triggerId}" |
|
|
|
|
|
|
|
data-repeat-count="${repeatCountVal}" |
|
|
|
data-play-url="${playUrl}">Lancer</button> |
|
|
|
data-play-url="${playUrl}">Lancer</button> |
|
|
|
<button type="button" class="small danger js-delete-trigger-btn" |
|
|
|
<button type="button" class="small danger js-delete-trigger-btn" |
|
|
|
data-trigger-id="${triggerId}">Supprimer</button> |
|
|
|
data-trigger-id="${triggerId}">Supprimer</button> |
|
|
|
@ -387,7 +400,9 @@ |
|
|
|
if (!ok) return; |
|
|
|
if (!ok) return; |
|
|
|
|
|
|
|
|
|
|
|
playBtn.disabled = true; |
|
|
|
playBtn.disabled = true; |
|
|
|
const body = new URLSearchParams({ trigger_id: tid }); |
|
|
|
const parsedRepeatCount = Number.parseInt(playBtn.dataset.repeatCount || row.dataset.triggerRepeatCount || "0", 10); |
|
|
|
|
|
|
|
const safeRepeatCount = Number.isFinite(parsedRepeatCount) && parsedRepeatCount >= 0 ? String(parsedRepeatCount) : "0"; |
|
|
|
|
|
|
|
const body = new URLSearchParams({ trigger_id: tid, repeat_count: safeRepeatCount }); |
|
|
|
fetch(url, { |
|
|
|
fetch(url, { |
|
|
|
method: "POST", |
|
|
|
method: "POST", |
|
|
|
headers: { "X-Requested-With": "fetch", "Content-Type": "application/x-www-form-urlencoded" }, |
|
|
|
headers: { "X-Requested-With": "fetch", "Content-Type": "application/x-www-form-urlencoded" }, |
|
|
|
@ -445,7 +460,9 @@ |
|
|
|
if (!ok) return; |
|
|
|
if (!ok) return; |
|
|
|
|
|
|
|
|
|
|
|
btn.disabled = true; |
|
|
|
btn.disabled = true; |
|
|
|
const body = new URLSearchParams({ trigger_id: triggerId }); |
|
|
|
const parsedRepeatCount = Number.parseInt(btn.dataset.repeatCount || row?.dataset.triggerRepeatCount || "0", 10); |
|
|
|
|
|
|
|
const safeRepeatCount = Number.isFinite(parsedRepeatCount) && parsedRepeatCount >= 0 ? String(parsedRepeatCount) : "0"; |
|
|
|
|
|
|
|
const body = new URLSearchParams({ trigger_id: triggerId, repeat_count: safeRepeatCount }); |
|
|
|
fetch(url, { |
|
|
|
fetch(url, { |
|
|
|
method: "POST", |
|
|
|
method: "POST", |
|
|
|
headers: { "X-Requested-With": "fetch", "Content-Type": "application/x-www-form-urlencoded" }, |
|
|
|
headers: { "X-Requested-With": "fetch", "Content-Type": "application/x-www-form-urlencoded" }, |
|
|
|
@ -483,6 +500,7 @@ |
|
|
|
const volumeDisplay = document.getElementById("trigger_volume_display"); |
|
|
|
const volumeDisplay = document.getElementById("trigger_volume_display"); |
|
|
|
const fadeInInput = document.getElementById("fade_in_seconds"); |
|
|
|
const fadeInInput = document.getElementById("fade_in_seconds"); |
|
|
|
const fadeOutInput = document.getElementById("fade_out_seconds"); |
|
|
|
const fadeOutInput = document.getElementById("fade_out_seconds"); |
|
|
|
|
|
|
|
const repeatCountInput = document.getElementById("repeat_count"); |
|
|
|
const normalizeInput = document.getElementById("normalize_audio"); |
|
|
|
const normalizeInput = document.getElementById("normalize_audio"); |
|
|
|
if (volumeInput && volumeDisplay) { |
|
|
|
if (volumeInput && volumeDisplay) { |
|
|
|
volumeInput.addEventListener("input", () => { volumeDisplay.textContent = volumeInput.value; }); |
|
|
|
volumeInput.addEventListener("input", () => { volumeDisplay.textContent = volumeInput.value; }); |
|
|
|
@ -535,6 +553,7 @@ |
|
|
|
} |
|
|
|
} |
|
|
|
if (fadeInInput) fadeInInput.value = row.dataset.triggerFadeIn || "0"; |
|
|
|
if (fadeInInput) fadeInInput.value = row.dataset.triggerFadeIn || "0"; |
|
|
|
if (fadeOutInput) fadeOutInput.value = row.dataset.triggerFadeOut || "0"; |
|
|
|
if (fadeOutInput) fadeOutInput.value = row.dataset.triggerFadeOut || "0"; |
|
|
|
|
|
|
|
if (repeatCountInput) repeatCountInput.value = row.dataset.triggerRepeatCount || "0"; |
|
|
|
if (normalizeInput) normalizeInput.checked = (row.dataset.triggerNormalize === "1"); |
|
|
|
if (normalizeInput) normalizeInput.checked = (row.dataset.triggerNormalize === "1"); |
|
|
|
|
|
|
|
|
|
|
|
typeInput?.focus(); |
|
|
|
typeInput?.focus(); |
|
|
|
@ -551,6 +570,21 @@ |
|
|
|
const closeBtn = document.getElementById("browser-audio-close"); |
|
|
|
const closeBtn = document.getElementById("browser-audio-close"); |
|
|
|
if (!player || !timeLabel || !nowPlaying || !modal || !closeBtn) return; |
|
|
|
if (!player || !timeLabel || !nowPlaying || !modal || !closeBtn) return; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
let previewName = "Fichier audio"; |
|
|
|
|
|
|
|
let repeatsRemaining = 0; |
|
|
|
|
|
|
|
let totalPlays = 1; |
|
|
|
|
|
|
|
let currentPlay = 1; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
const parseRepeatCount = (rawValue) => { |
|
|
|
|
|
|
|
const parsed = Number.parseInt(String(rawValue ?? ""), 10); |
|
|
|
|
|
|
|
if (!Number.isFinite(parsed) || parsed < 0) return 0; |
|
|
|
|
|
|
|
return parsed; |
|
|
|
|
|
|
|
}; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
const updateNowPlaying = () => { |
|
|
|
|
|
|
|
nowPlaying.textContent = "Lecture: " + previewName + " (" + currentPlay + "/" + totalPlays + ")"; |
|
|
|
|
|
|
|
}; |
|
|
|
|
|
|
|
|
|
|
|
const openModal = () => { |
|
|
|
const openModal = () => { |
|
|
|
modal.classList.add("is-open"); |
|
|
|
modal.classList.add("is-open"); |
|
|
|
modal.setAttribute("aria-hidden", "false"); |
|
|
|
modal.setAttribute("aria-hidden", "false"); |
|
|
|
@ -558,6 +592,9 @@ |
|
|
|
|
|
|
|
|
|
|
|
const closeModal = () => { |
|
|
|
const closeModal = () => { |
|
|
|
player.pause(); |
|
|
|
player.pause(); |
|
|
|
|
|
|
|
repeatsRemaining = 0; |
|
|
|
|
|
|
|
totalPlays = 1; |
|
|
|
|
|
|
|
currentPlay = 1; |
|
|
|
modal.classList.remove("is-open"); |
|
|
|
modal.classList.remove("is-open"); |
|
|
|
modal.setAttribute("aria-hidden", "true"); |
|
|
|
modal.setAttribute("aria-hidden", "true"); |
|
|
|
}; |
|
|
|
}; |
|
|
|
@ -578,7 +615,18 @@ |
|
|
|
|
|
|
|
|
|
|
|
player.addEventListener("timeupdate", updateTimeLabel); |
|
|
|
player.addEventListener("timeupdate", updateTimeLabel); |
|
|
|
player.addEventListener("loadedmetadata", updateTimeLabel); |
|
|
|
player.addEventListener("loadedmetadata", updateTimeLabel); |
|
|
|
player.addEventListener("ended", updateTimeLabel); |
|
|
|
player.addEventListener("ended", () => { |
|
|
|
|
|
|
|
if (repeatsRemaining <= 0) { |
|
|
|
|
|
|
|
updateTimeLabel(); |
|
|
|
|
|
|
|
return; |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
repeatsRemaining -= 1; |
|
|
|
|
|
|
|
currentPlay += 1; |
|
|
|
|
|
|
|
player.currentTime = 0; |
|
|
|
|
|
|
|
player.play().catch(() => {}); |
|
|
|
|
|
|
|
updateNowPlaying(); |
|
|
|
|
|
|
|
updateTimeLabel(); |
|
|
|
|
|
|
|
}); |
|
|
|
|
|
|
|
|
|
|
|
const playButtons = document.querySelectorAll(".js-play-browser"); |
|
|
|
const playButtons = document.querySelectorAll(".js-play-browser"); |
|
|
|
playButtons.forEach((button) => { button.addEventListener("click", () => { |
|
|
|
playButtons.forEach((button) => { button.addEventListener("click", () => { |
|
|
|
@ -586,13 +634,25 @@ |
|
|
|
const name = button.dataset.audioName || "Fichier audio"; |
|
|
|
const name = button.dataset.audioName || "Fichier audio"; |
|
|
|
if (!src) return; |
|
|
|
if (!src) return; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
const triggerRepeatInput = document.getElementById("repeat_count"); |
|
|
|
|
|
|
|
const repeatCount = button.dataset.repeatCount != null |
|
|
|
|
|
|
|
? parseRepeatCount(button.dataset.repeatCount) |
|
|
|
|
|
|
|
: parseRepeatCount(triggerRepeatInput?.value); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
previewName = name; |
|
|
|
|
|
|
|
repeatsRemaining = repeatCount; |
|
|
|
|
|
|
|
totalPlays = repeatCount + 1; |
|
|
|
|
|
|
|
currentPlay = 1; |
|
|
|
|
|
|
|
|
|
|
|
if (player.src !== src) { |
|
|
|
if (player.src !== src) { |
|
|
|
player.src = src; |
|
|
|
player.src = src; |
|
|
|
|
|
|
|
} else { |
|
|
|
|
|
|
|
player.currentTime = 0; |
|
|
|
} |
|
|
|
} |
|
|
|
player.play().catch(() => { |
|
|
|
player.play().catch(() => { |
|
|
|
// Browsers may block autoplay in some contexts; user can press play manually. |
|
|
|
// Browsers may block autoplay in some contexts; user can press play manually. |
|
|
|
}); |
|
|
|
}); |
|
|
|
nowPlaying.textContent = "Lecture: " + name; |
|
|
|
updateNowPlaying(); |
|
|
|
updateTimeLabel(); |
|
|
|
updateTimeLabel(); |
|
|
|
openModal(); |
|
|
|
openModal(); |
|
|
|
}); |
|
|
|
}); |
|
|
|
@ -795,8 +855,10 @@ |
|
|
|
|
|
|
|
|
|
|
|
let previewContext = null; |
|
|
|
let previewContext = null; |
|
|
|
let previewSource = null; |
|
|
|
let previewSource = null; |
|
|
|
|
|
|
|
let previewSessionId = 0; |
|
|
|
|
|
|
|
|
|
|
|
const stopPreview = () => { |
|
|
|
const stopPreview = () => { |
|
|
|
|
|
|
|
previewSessionId += 1; |
|
|
|
if (previewSource) { |
|
|
|
if (previewSource) { |
|
|
|
try { |
|
|
|
try { |
|
|
|
previewSource.stop(); |
|
|
|
previewSource.stop(); |
|
|
|
@ -842,6 +904,7 @@ |
|
|
|
const volumeInput = document.getElementById("trigger_volume"); |
|
|
|
const volumeInput = document.getElementById("trigger_volume"); |
|
|
|
const fadeInInput = document.getElementById("fade_in_seconds"); |
|
|
|
const fadeInInput = document.getElementById("fade_in_seconds"); |
|
|
|
const fadeOutInput = document.getElementById("fade_out_seconds"); |
|
|
|
const fadeOutInput = document.getElementById("fade_out_seconds"); |
|
|
|
|
|
|
|
const repeatCountInput = document.getElementById("repeat_count"); |
|
|
|
const normalizeInput = document.getElementById("normalize_audio"); |
|
|
|
const normalizeInput = document.getElementById("normalize_audio"); |
|
|
|
|
|
|
|
|
|
|
|
const startSeconds = asNonNegative(startInput, 0); |
|
|
|
const startSeconds = asNonNegative(startInput, 0); |
|
|
|
@ -850,6 +913,7 @@ |
|
|
|
const volume = asNonNegative(volumeInput, 80); |
|
|
|
const volume = asNonNegative(volumeInput, 80); |
|
|
|
const fadeInSeconds = asNonNegative(fadeInInput, 0); |
|
|
|
const fadeInSeconds = asNonNegative(fadeInInput, 0); |
|
|
|
const fadeOutSeconds = asNonNegative(fadeOutInput, 0); |
|
|
|
const fadeOutSeconds = asNonNegative(fadeOutInput, 0); |
|
|
|
|
|
|
|
const repeatCount = Math.max(0, Math.floor(asNonNegative(repeatCountInput, 0))); |
|
|
|
const normalizeAudio = !!normalizeInput?.checked; |
|
|
|
const normalizeAudio = !!normalizeInput?.checked; |
|
|
|
|
|
|
|
|
|
|
|
btn.disabled = true; |
|
|
|
btn.disabled = true; |
|
|
|
@ -894,21 +958,25 @@ |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
const playDuration = clampedEnd - startSeconds; |
|
|
|
const playDuration = clampedEnd - startSeconds; |
|
|
|
|
|
|
|
const normalizeGain = normalizeAudio ? computeNormalizeGain(audioBuffer, startSeconds, playDuration) : 1; |
|
|
|
|
|
|
|
const baseGain = Math.max(0, Math.min(1, volume / 100)) * normalizeGain; |
|
|
|
|
|
|
|
const fadeIn = Math.min(Math.max(0, fadeInSeconds), playDuration); |
|
|
|
|
|
|
|
const fadeOut = Math.min(Math.max(0, fadeOutSeconds), playDuration); |
|
|
|
|
|
|
|
const currentSession = previewSessionId; |
|
|
|
|
|
|
|
const totalPlays = repeatCount + 1; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
const startPreviewPlay = (playIndex) => { |
|
|
|
|
|
|
|
if (currentSession !== previewSessionId) return; |
|
|
|
|
|
|
|
|
|
|
|
const source = previewContext.createBufferSource(); |
|
|
|
const source = previewContext.createBufferSource(); |
|
|
|
source.buffer = audioBuffer; |
|
|
|
source.buffer = audioBuffer; |
|
|
|
const gainNode = previewContext.createGain(); |
|
|
|
const gainNode = previewContext.createGain(); |
|
|
|
source.connect(gainNode); |
|
|
|
source.connect(gainNode); |
|
|
|
gainNode.connect(previewContext.destination); |
|
|
|
gainNode.connect(previewContext.destination); |
|
|
|
|
|
|
|
|
|
|
|
const normalizeGain = normalizeAudio ? computeNormalizeGain(audioBuffer, startSeconds, playDuration) : 1; |
|
|
|
|
|
|
|
const baseGain = Math.max(0, Math.min(1, volume / 100)) * normalizeGain; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
const now = previewContext.currentTime; |
|
|
|
const now = previewContext.currentTime; |
|
|
|
const startAt = now + 0.03; |
|
|
|
const startAt = now + 0.03; |
|
|
|
const endAt = startAt + playDuration; |
|
|
|
const endAt = startAt + playDuration; |
|
|
|
|
|
|
|
|
|
|
|
const fadeIn = Math.min(Math.max(0, fadeInSeconds), playDuration); |
|
|
|
|
|
|
|
const fadeOut = Math.min(Math.max(0, fadeOutSeconds), playDuration); |
|
|
|
|
|
|
|
const fadeOutStart = Math.max(startAt + fadeIn, endAt - fadeOut); |
|
|
|
const fadeOutStart = Math.max(startAt + fadeIn, endAt - fadeOut); |
|
|
|
|
|
|
|
|
|
|
|
gainNode.gain.cancelScheduledValues(now); |
|
|
|
gainNode.gain.cancelScheduledValues(now); |
|
|
|
@ -929,6 +997,10 @@ |
|
|
|
if (previewSource === source) { |
|
|
|
if (previewSource === source) { |
|
|
|
previewSource = null; |
|
|
|
previewSource = null; |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
if (currentSession !== previewSessionId) return; |
|
|
|
|
|
|
|
if (playIndex < totalPlays) { |
|
|
|
|
|
|
|
startPreviewPlay(playIndex + 1); |
|
|
|
|
|
|
|
} |
|
|
|
}; |
|
|
|
}; |
|
|
|
|
|
|
|
|
|
|
|
previewSource = source; |
|
|
|
previewSource = source; |
|
|
|
@ -936,8 +1008,12 @@ |
|
|
|
|
|
|
|
|
|
|
|
if (nowPlaying) { |
|
|
|
if (nowPlaying) { |
|
|
|
nowPlaying.textContent = "Prévisualisation: " + filename + |
|
|
|
nowPlaying.textContent = "Prévisualisation: " + filename + |
|
|
|
" (" + startSeconds.toFixed(1) + "s -> " + clampedEnd.toFixed(1) + "s)"; |
|
|
|
" (" + startSeconds.toFixed(1) + "s -> " + clampedEnd.toFixed(1) + "s)" + |
|
|
|
|
|
|
|
" [" + playIndex + "/" + totalPlays + "]"; |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
}; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
startPreviewPlay(1); |
|
|
|
player?.closest("section")?.scrollIntoView({ behavior: "smooth", block: "nearest" }); |
|
|
|
player?.closest("section")?.scrollIntoView({ behavior: "smooth", block: "nearest" }); |
|
|
|
}) |
|
|
|
}) |
|
|
|
.catch((err) => { |
|
|
|
.catch((err) => { |
|
|
|
|