From 08fafbf2de070b4e45026272bd3c907e9f8ba263 Mon Sep 17 00:00:00 2001 From: Nuwan Date: Tue, 10 Dec 2024 11:23:02 +0530 Subject: [PATCH] work on JamTrack player --- .../components/jamtracks/JKCreateCustomMix.js | 6 +- .../components/jamtracks/JKJamTrackPlayer.js | 116 ++++++++++++++++-- .../components/jamtracks/JKMyJamTrackMixes.js | 16 ++- jam-ui/src/helpers/rest.js | 12 ++ jam-ui/src/store/features/jamTrackSlice.js | 111 ++++++++++++----- .../api_jam_track_mixdowns_controller.rb | 1 + 6 files changed, 210 insertions(+), 52 deletions(-) diff --git a/jam-ui/src/components/jamtracks/JKCreateCustomMix.js b/jam-ui/src/components/jamtracks/JKCreateCustomMix.js index 861e15da9..08e84a8af 100644 --- a/jam-ui/src/components/jamtracks/JKCreateCustomMix.js +++ b/jam-ui/src/components/jamtracks/JKCreateCustomMix.js @@ -185,7 +185,7 @@ const JKCreateCustomMix = () => { Tracks {tracks.length > 0 && <>({tracks.length})} - Mute + Mute @@ -196,8 +196,8 @@ const JKCreateCustomMix = () => { {trackName(track)} - - + + ))} diff --git a/jam-ui/src/components/jamtracks/JKJamTrackPlayer.js b/jam-ui/src/components/jamtracks/JKJamTrackPlayer.js index c3c131846..244ab30d1 100644 --- a/jam-ui/src/components/jamtracks/JKJamTrackPlayer.js +++ b/jam-ui/src/components/jamtracks/JKJamTrackPlayer.js @@ -2,7 +2,10 @@ import React, { useState, useEffect, useRef } from 'react'; import Select from 'react-select'; import { Row, Col } from 'reactstrap'; import FingerprintJS from '@fingerprintjs/fingerprintjs'; -import { useSelector } from 'react-redux'; +import { useSelector, useDispatch } from 'react-redux'; +import JKModalDialog from '../common/JKModalDialog'; +import { enqueueMyMixdown } from '../../store/features/jamTrackSlice'; +import { set } from 'react-hook-form'; const JKJamTrackPlayer = () => { const [options, setOptions] = useState([]); @@ -11,10 +14,17 @@ const JKJamTrackPlayer = () => { const [audioUrl, setAudioUrl] = useState(null); const audioRef = useRef(null); const jamTrack = useSelector(state => state.jamTrack.jamTrack); + const mixdownsLoadingStatus = useSelector(state => state.jamTrack.mixdownsLoadingStatus); + const SAMPLE_RATE = 48; + const [retryMessage, setRetryMessage] = useState(''); + const [showRetry, setShowRetry] = useState(false); + const [retryDownload, setRetryDownload] = useState(false); + + const dispatch = useDispatch(); useEffect(() => { if (jamTrack) { - const opts = jamTrack.mixdowns.map(mix => ({ value: mix.id, label: mix.name })).filter(mix => mix.value !== 'temp'); + const opts = jamTrack.mixdowns.filter(mix => mix.id !== 'temp').map(mix => ({ value: mix.id, label: mix.name })); opts.unshift({ value: 'original', label: 'Original' }); setOptions(opts); if (jamTrack.last_mixdown_id) { @@ -25,6 +35,16 @@ const JKJamTrackPlayer = () => { } }, [jamTrack]); + const audioFileExtension = url => { + try { + const u = new URL(url); + const ext = u.pathname.split('.').pop(); + return `audio/${ext}`; + } catch (e) { + return ''; + } + }; + const handleOnChange = selectedOption => { const option = options.find(opt => opt.value === selectedOption.value); setSelectedOption(option); @@ -35,22 +55,76 @@ const JKJamTrackPlayer = () => { return; } + console.log('selectedOption', selectedOption); if (selectedOption.value === 'original') { - const audioUrl = getMasterTrack(); + const audioUrl = getOriginalTrackUrl(); setAudioUrl(audioUrl); - if(audioRef.current) + if (audioRef.current) { audioRef.current.load(); + } } else { //it's a mixdown - getMixdown().then(audioUrl => { + //see if there is a myPackage avaialble. if it is, it means the mixdown is ready + //if not, it means the mixdown is still being processed + + const myPackage = jamTrack.mixdowns + .find(mix => mix.id === selectedOption.value) + ?.packages.find(p => p.file_type === 'mp3' && p.encrypt_type === null && p.sample_rate === SAMPLE_RATE); + + console.log('_DEBUG_ myPackage', myPackage); + + let retry = false; + if (myPackage) { + //let see if the mixdown is signed + switch (myPackage.signing_state) { + case 'QUIET_TIMEOUT': + setRetryMessage('Custom mix never got created. Retry?'); + retry = true; + break; + case 'QUEUED_TIMEOUT': + setRetryMessage('Custom mix was never built. Retry?'); + retry = true; + break; + case 'SIGNING_TIMEOUT': + setRetryMessage('Custom mix took took long to build. Retry?'); + retry = true; + break; + case 'ERROR': + setRetryMessage('Custom mix failed to build. Retry?'); + retry = true; + break; + default: + break; + } + } else { + setRetryMessage('Custom mix never got created. Retry?'); + retry = true; + setAudioUrl(null); + } + + if (retry) { + setShowRetry(true); + return; + } + + getActiveMixdownUrl().then(audioUrl => { + console.log('_DEBUG_ audioUrl', audioUrl); setAudioUrl(audioUrl); - if(audioRef.current) + if (audioRef.current) { audioRef.current.load(); + } }); } }, [selectedOption]); - const getMasterTrack = () => { + useEffect(() => { + if (retryDownload && selectedOption) { + const options = { id: selectedOption.value, file_type: 'mp3', encrypt_type: null, sample_rate: SAMPLE_RATE }; + dispatch(enqueueMyMixdown(options)); + } + }, [retryDownload]); + + const getOriginalTrackUrl = () => { const masterTrack = jamTrack.tracks.find(track => track.track_type === 'Master'); if (masterTrack) { const audioUrl = masterTrack.preview_mp3_url; @@ -58,14 +132,13 @@ const JKJamTrackPlayer = () => { } }; - const getMixdown = async () => { + const getActiveMixdownUrl = async () => { const activeMix = jamTrack.mixdowns.find(mix => mix.id === selectedOption.value); const fp = await fpPromise; const result = await fp.get(); const audioUrl = process.env.REACT_APP_API_BASE_URL + `/mixdowns/${activeMix.id}/download.mp3?file_type=mp3&sample_rate=48&mark=${result.visitorId}`; - console.log('audioUrl', audioUrl); return audioUrl; }; @@ -77,14 +150,35 @@ const JKJamTrackPlayer = () => { {audioUrl && (
)} + + setRetryDownload(!showRetry)} + title="Retry Download?" + showFooter={false} + > +
+

{retryMessage}

+ +
+
); }; -export default JKJamTrackPlayer; \ No newline at end of file +export default JKJamTrackPlayer; diff --git a/jam-ui/src/components/jamtracks/JKMyJamTrackMixes.js b/jam-ui/src/components/jamtracks/JKMyJamTrackMixes.js index 5d170f93f..627886aa3 100644 --- a/jam-ui/src/components/jamtracks/JKMyJamTrackMixes.js +++ b/jam-ui/src/components/jamtracks/JKMyJamTrackMixes.js @@ -14,6 +14,7 @@ const JKMyJamTrackMixes = () => { const mixdownsLoadingStatus = useSelector(state => state.jamTrack.mixdownsLoadingStatus); const deleteMixdownStatus = useSelector(state => state.jamTrack.deleteMixdownStatus); const tempMixdownLoadingStatus = useSelector(state => state.jamTrack.tempMixdownLoadingStatus); + const buildRetries = useSelector(state => state.jamTrack.buildRetries); useEffect(() => { if (!jamTrack) { @@ -74,6 +75,11 @@ const JKMyJamTrackMixes = () => { } }; + const isMixdownBuilding = mix => { + const retry = buildRetries.find(retry => retry.mixdownId === mix.id); + return mix.id === 'temp' || (retry && ['queued', 'enqueuing'].includes(retry.state)) + } + return ( <>

@@ -84,13 +90,13 @@ const JKMyJamTrackMixes = () => { Mix - Actions + Actions Full JamTrack - + @@ -99,8 +105,8 @@ const JKMyJamTrackMixes = () => { {mixes.map(mix => ( {mix.name} - - {mix.id === 'temp' ? ( + + {isMixdownBuilding(mix) ? ( ) : ( <> @@ -112,7 +118,7 @@ const JKMyJamTrackMixes = () => { disabled={deleteMixdownStatus === 'loading'} style={{ cursor: 'pointer' }} > - + )} diff --git a/jam-ui/src/helpers/rest.js b/jam-ui/src/helpers/rest.js index db8a4e31a..8c57e8c79 100644 --- a/jam-ui/src/helpers/rest.js +++ b/jam-ui/src/helpers/rest.js @@ -576,6 +576,18 @@ export const createMixdown = options => { }); } +export const enqueueMixdown = options => { + const { id, ...rest } = options; + return new Promise((resolve, reject) => { + apiFetch(`/mixdowns/${id}/enqueue`, { + method: 'POST', + body: JSON.stringify(rest) + }) + .then(response => resolve(response)) + .catch(error => reject(error)); + }); +} + export const deleteMixdown = id => { return new Promise((resolve, reject) => { apiFetch(`/mixdowns/${id}`, { diff --git a/jam-ui/src/store/features/jamTrackSlice.js b/jam-ui/src/store/features/jamTrackSlice.js index 251c704f9..6c623304a 100644 --- a/jam-ui/src/store/features/jamTrackSlice.js +++ b/jam-ui/src/store/features/jamTrackSlice.js @@ -1,5 +1,5 @@ import { createSlice, createAsyncThunk } from '@reduxjs/toolkit'; -import { deleteMixdown, getJamTrack, createMixdown } from '../../helpers/rest'; +import { deleteMixdown, getJamTrack, createMixdown, enqueueMixdown } from '../../helpers/rest'; const initialState = { jamTrack: {}, @@ -8,27 +8,33 @@ const initialState = { deleteMixdownStatus: 'idle', newMixdownLoadingStatus: 'idle', tempMixdownLoadingStatus: 'idle', - + enqueueLoadingStatus: 'idle', + buildRetries: [], error: null -} +}; -export const fetchJamTrack = createAsyncThunk('jamTracks/fetchJamTrack', async(options, thunkAPI) => { - const response = await getJamTrack(options) +export const fetchJamTrack = createAsyncThunk('jamTracks/fetchJamTrack', async (options, thunkAPI) => { + const response = await getJamTrack(options); return response.json(); }); -export const createMyMixdown = createAsyncThunk('jamTracks/createMixdown', async(options, thunkAPI) => { - const response = await createMixdown(options) +export const createMyMixdown = createAsyncThunk('jamTracks/createMixdown', async (options, thunkAPI) => { + const response = await createMixdown(options); return response.json(); }); -export const removeMixdown = createAsyncThunk('jamTracks/removeMixdown', async(options, thunkAPI) => { +export const removeMixdown = createAsyncThunk('jamTracks/removeMixdown', async (options, thunkAPI) => { console.log('removeMixdown', options); const { id } = options; - const response = await deleteMixdown(id) + const response = await deleteMixdown(id); return { id }; }); +export const enqueueMyMixdown = createAsyncThunk('jamTracks/enqueueMixdown', async (options, thunkAPI) => { + const response = await enqueueMixdown(options); + return response.json(); +}); + export const jamTrackSlice = createSlice({ name: 'jamTrack', initialState, @@ -40,62 +46,101 @@ export const jamTrackSlice = createSlice({ state.jamTrack.mixdowns = [...jamTrack.mixdowns, payload]; state.tempMixdownLoadingStatus = 'succeeded'; } - }, + } }, extraReducers: builder => { builder .addCase(fetchJamTrack.pending, (state, action) => { - state.jamTrackLoadingStatus = 'loading' - state.mixdownsLoadingStatus = 'loading' + state.jamTrackLoadingStatus = 'loading'; + state.mixdownsLoadingStatus = 'loading'; }) .addCase(fetchJamTrack.fulfilled, (state, action) => { - state.jamTrack = action.payload - state.jamTrackLoadingStatus = 'succeeded' + state.jamTrack = action.payload; + state.jamTrackLoadingStatus = 'succeeded'; if (action.payload.mixdowns) { - state.mixdownsLoadingStatus = 'succeeded' + state.mixdownsLoadingStatus = 'succeeded'; } }) .addCase(fetchJamTrack.rejected, (state, action) => { - state.status = 'failed' - state.jamTrackLoadingStatus = 'failed' - state.mixdownsLoadingStatus = 'failed' + state.status = 'failed'; + state.jamTrackLoadingStatus = 'failed'; + state.mixdownsLoadingStatus = 'failed'; state.error = action.error.message; }) .addCase(createMyMixdown.pending, (state, action) => { - state.newMixdownLoadingStatus = 'loading' - state.mixdownsLoadingStatus = 'loading' + state.newMixdownLoadingStatus = 'loading'; + state.mixdownsLoadingStatus = 'loading'; }) .addCase(createMyMixdown.fulfilled, (state, action) => { state.jamTrack.mixdowns = [...state.jamTrack.mixdowns, action.payload]; - state.newMixdownLoadingStatus = 'succeeded' - state.mixdownsLoadingStatus = 'succeeded' - state.tempMixdownLoadingStatus = 'idle' + state.newMixdownLoadingStatus = 'succeeded'; + state.mixdownsLoadingStatus = 'succeeded'; + state.tempMixdownLoadingStatus = 'idle'; }) .addCase(createMyMixdown.rejected, (state, action) => { state.error = action.error.message; - state.newMixdownLoadingStatus = 'failed' - state.tempMixdownLoadingStatus = 'idle' + state.newMixdownLoadingStatus = 'failed'; + state.tempMixdownLoadingStatus = 'idle'; }) .addCase(removeMixdown.pending, (state, action) => { - state.mixdownsLoadingStatus = 'loading' - state.deleteMixdownStatus = 'loading' + state.mixdownsLoadingStatus = 'loading'; + state.deleteMixdownStatus = 'loading'; }) .addCase(removeMixdown.fulfilled, (state, action) => { - console.log('mixdown removed', action.payload) + console.log('mixdown removed', action.payload); const mixdowns = state.jamTrack.mixdowns.filter(mix => mix.id !== action.payload.id); state.jamTrack.mixdowns = mixdowns; state.mixdowns = mixdowns; - state.mixdownsLoadingStatus = 'succeeded' - state.deleteMixdownStatus = 'succeeded' + state.mixdownsLoadingStatus = 'succeeded'; + state.deleteMixdownStatus = 'succeeded'; }) .addCase(removeMixdown.rejected, (state, action) => { state.error = action.error.message; - state.mixdownsLoadingStatus = 'failed' - state.deleteMixdownStatus = 'failed' + state.mixdownsLoadingStatus = 'failed'; + state.deleteMixdownStatus = 'failed'; }) + .addCase(enqueueMyMixdown.pending, (state, action) => { + const estimate = { mixdownId: action.meta.arg.id, state: 'enqueuing' }; + if (!state.buildRetries.find(estimate => estimate.mixdownId === action.meta.arg.id)) { + state.buildRetries = [...state.buildRetries, estimate]; + } else { + state.buildRetries = state.buildRetries.map(estimate => { + if (estimate.mixdownId === action.meta.arg.id) { + return { ...estimate, state: 'enqueuing' }; + } + return estimate; + }); + } + }) + .addCase(enqueueMyMixdown.fulfilled, (state, action) => { + const estimate = { mixdownId: action.payload.id, time: action.payload.queue_time, state: 'queued' }; + if (!state.buildRetries.find(estimate => estimate.mixdownId === action.payload.id)) { + state.buildRetries = [...state.buildRetries, estimate]; + } else { + state.buildRetries = state.buildRetries.map(estimate => { + if (estimate.mixdownId === action.payload.id) { + return { ...estimate, ...action.payload, state: 'queued' }; + } + return estimate; + }); + } + }) + .addCase(enqueueMyMixdown.rejected, (state, action) => { + const estimate = { mixdownId: action.meta.arg.id, state: 'failed' }; + if (!state.buildRetries.find(estimate => estimate.mixdownId === action.meta.arg.id)) { + state.buildRetries = [...state.buildRetries, estimate]; + } else { + state.buildRetries = state.buildRetries.map(estimate => { + if (estimate.mixdownId === action.meta.arg.id) { + return { ...estimate, state: 'failed' }; + } + return estimate; + }); + } + }); } }); export const { addMixdown } = jamTrackSlice.actions; -export default jamTrackSlice.reducer; \ No newline at end of file +export default jamTrackSlice.reducer; diff --git a/web/app/controllers/api_jam_track_mixdowns_controller.rb b/web/app/controllers/api_jam_track_mixdowns_controller.rb index 53b121b0f..9efd1ca5d 100644 --- a/web/app/controllers/api_jam_track_mixdowns_controller.rb +++ b/web/app/controllers/api_jam_track_mixdowns_controller.rb @@ -118,6 +118,7 @@ class ApiJamTrackMixdownsController < ApiController end def enqueue + debugger if @jam_track_right.valid? begin