work on JamTrack player

This commit is contained in:
Nuwan 2024-12-10 11:23:02 +05:30
parent cbaf1ea4f9
commit 08fafbf2de
6 changed files with 210 additions and 52 deletions

View File

@ -185,7 +185,7 @@ const JKCreateCustomMix = () => {
<thead className="bg-200 text-900">
<tr>
<th>Tracks {tracks.length > 0 && <>({tracks.length})</>}</th>
<th class="text-center">Mute</th>
<th className="text-center">Mute</th>
</tr>
</thead>
<tbody>
@ -196,8 +196,8 @@ const JKCreateCustomMix = () => {
<td>
<span>{trackName(track)}</span>
</td>
<td class="text-center">
<input type="checkbox" value={track.id} onClick={toggleTrack} checked={ selectedTracks.includes(track.id)} disabled={hasExceededMax} />
<td className="text-center">
<input type="checkbox" value={track.id} onChange={toggleTrack} checked={ selectedTracks.includes(track.id)} disabled={hasExceededMax} />
</td>
</tr>
))}

View File

@ -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 && (
<figure>
<audio controls style={{ width: '100%' }} ref={audioRef}>
<source src={audioUrl} type={`audio/${audioUrl.split('.').pop()}`} />
<source src={audioUrl} type={audioFileExtension(audioUrl)} />
</audio>
</figure>
)}
</Col>
</Row>
<JKModalDialog
show={showRetry}
onToggle={() => setRetryDownload(!showRetry)}
title="Retry Download?"
showFooter={false}
>
<div className="d-flex flex-column">
<p>{retryMessage}</p>
<button
className="btn btn-primary"
onClick={() => {
setRetryDownload(true);
setShowRetry(false);
setRetryMessage('');
}}
>
Yes
</button>
</div>
</JKModalDialog>
</>
);
};
export default JKJamTrackPlayer;
export default JKJamTrackPlayer;

View File

@ -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 (
<>
<p>
@ -84,13 +90,13 @@ const JKMyJamTrackMixes = () => {
<thead className="bg-200 text-900">
<tr>
<th>Mix</th>
<th class="text-center">Actions</th>
<th className="text-center">Actions</th>
</tr>
</thead>
<tbody>
<tr>
<td>Full JamTrack</td>
<td class="text-center">
<td className="text-center">
<a onClick={downloadJamTrack}>
<FontAwesomeIcon icon="download" size="lg" className="mr-3" />
</a>
@ -99,8 +105,8 @@ const JKMyJamTrackMixes = () => {
{mixes.map(mix => (
<tr key={mix.id}>
<td>{mix.name}</td>
<td class="text-center">
{mix.id === 'temp' ? (
<td className="text-center">
{isMixdownBuilding(mix) ? (
<FontAwesomeIcon icon="spinner" size="lg" />
) : (
<>
@ -112,7 +118,7 @@ const JKMyJamTrackMixes = () => {
disabled={deleteMixdownStatus === 'loading'}
style={{ cursor: 'pointer' }}
>
<FontAwesomeIcon icon="trash" size="xl" />
<FontAwesomeIcon icon="trash" size="lg" />
</a>
</>
)}

View File

@ -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}`, {

View File

@ -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;
export default jamTrackSlice.reducer;

View File

@ -118,6 +118,7 @@ class ApiJamTrackMixdownsController < ApiController
end
def enqueue
debugger
if @jam_track_right.valid?
begin