wip jamtrack page creating and playing custom jamtracks

This commit is contained in:
Nuwan 2024-12-17 16:22:31 +05:30
parent 08fafbf2de
commit c3563a9197
13 changed files with 400 additions and 186 deletions

View File

@ -160,6 +160,7 @@ function JKDashboardMain() {
registerFriendRequest(); registerFriendRequest();
registerFriendRequestAccepted(); registerFriendRequestAccepted();
registerChatMessageCallback(); registerChatMessageCallback();
registerSubscriptionCallback();
scriptLoaded.current = true; scriptLoaded.current = true;
}; };
@ -242,6 +243,14 @@ function JKDashboardMain() {
}); });
}; };
const registerSubscriptionCallback = () => {
window.JK.JamServer.registerMessageCallback(window.JK.MessageType.SUBSCRIPTION_MESSAGE, function(header, payload) {
console.log('registerSubscriptionCallback payload', payload);
console.log('registerSubscriptionCallback header', header);
handleSubscriptionMessage(payload);
});
};
const handleNotification = (payload, type) => { const handleNotification = (payload, type) => {
console.log('handleNotification', payload, type); console.log('handleNotification', payload, type);
const notification = { const notification = {
@ -266,6 +275,10 @@ function JKDashboardMain() {
} }
}; };
const handleSubscriptionMessage = payload => {
}
useScript(`${process.env.REACT_APP_CLIENT_BASE_URL}/client_scripts`, initJKScripts); useScript(`${process.env.REACT_APP_CLIENT_BASE_URL}/client_scripts`, initJKScripts);
return ( return (

View File

@ -1,19 +1,30 @@
import React, { useState, useEffect, useRef } from 'react'; import React, { useState, useEffect, useRef } from 'react';
import { Table, Row, Col, Input, Button } from 'reactstrap'; import { Table, Row, Col, Input, Button } from 'reactstrap';
import Select from 'react-select'; import Select from 'react-select';
import { useForm, Controller } from 'react-hook-form'; import { useForm, Controller, set } from 'react-hook-form';
import { createMyMixdown, addMixdown } from '../../store/features/jamTrackSlice'; import {
createMyMixdown,
enqueueMyMixdown,
addWatchedMixdown,
removeWatchedMixdown
} from '../../store/features/jamTrackSlice';
import { useDispatch, useSelector } from 'react-redux'; import { useDispatch, useSelector } from 'react-redux';
import { Scrollbar } from 'react-scrollbars-custom'; import { Scrollbar } from 'react-scrollbars-custom';
import { SAMPLE_RATE } from '../../helpers/jamTracks';
import JKModalDialog from '../common/JKModalDialog';
import { createAlert } from '../../helpers/rest';
import { useAuth } from '../../context/UserAuth';
const JKCreateCustomMix = () => { const JKCreateCustomMix = () => {
const MAX_MIXDOWNS = 5; const MAX_MIXDOWNS = 5;
const [tracks, setTracks] = useState([]); const [tracks, setTracks] = useState([]);
const [mixdowns, setMixdowns] = useState([]);
const [selectedTracks, setSelectedTracks] = useState([]); const [selectedTracks, setSelectedTracks] = useState([]);
const [showQueueTime, setShowQueueTime] = useState(false);
const [enqueueTimeMessage, setEnqueueTimeMessage] = useState('');
const dispatch = useDispatch(); const dispatch = useDispatch();
const scrollbar = useRef(); const scrollbar = useRef();
const { currentUser} = useAuth();
const TEMPO_OPTIONS = [ const TEMPO_OPTIONS = [
{ value: '0', label: 'Original tempo' }, { value: '0', label: 'Original tempo' },
@ -68,7 +79,12 @@ const JKCreateCustomMix = () => {
]; ];
const jamTrack = useSelector(state => state.jamTrack.jamTrack); const jamTrack = useSelector(state => state.jamTrack.jamTrack);
const mixdowns = useSelector(state => state.jamTrack.mixdowns);
const newMixdownLoadingStatus = useSelector(state => state.jamTrack.newMixdownLoadingStatus); const newMixdownLoadingStatus = useSelector(state => state.jamTrack.newMixdownLoadingStatus);
const awaitingMixdown = useSelector(state => state.jamTrack.awaitingMixdown);
const enqueuedMixdown = useSelector(state => state.jamTrack.enqueuedMixdown);
const enqueuedMixdowns = useSelector(state => state.jamTrack.enqueuedMixdowns);
const watchedMixdowns = useSelector(state => state.jamTrack.watchedMixdowns);
const { const {
control, control,
@ -114,11 +130,16 @@ const JKCreateCustomMix = () => {
const mixData = { const mixData = {
jamTrackID: jamTrack.id, jamTrackID: jamTrack.id,
name: data.mixName, name: data.mixName,
settings: { speed: parseInt(data.tempo.value), pitch: parseInt(data.pitch.value), 'count-in': countIn, tracks: _tracks } settings: {
speed: parseInt(data.tempo.value),
pitch: parseInt(data.pitch.value),
'count-in': countIn,
tracks: _tracks
}
}; };
const tempMixdown = {...mixData, id: 'temp', jam_track_id: jamTrack.id}; //const tempMixdown = { ...mixData, id: 'temp', jam_track_id: jamTrack.id };
dispatch(addMixdown(tempMixdown)); //dispatch(addMixdown(tempMixdown));
dispatch(createMyMixdown(mixData)); dispatch(createMyMixdown(mixData));
}; };
@ -137,23 +158,133 @@ const JKCreateCustomMix = () => {
useEffect(() => { useEffect(() => {
if (jamTrack) { if (jamTrack) {
setTracks(jamTrack.tracks.filter(track => track.track_type === 'Track' || track.track_type === 'Click')); setTracks(jamTrack.tracks.filter(track => track.track_type === 'Track' || track.track_type === 'Click'));
setMixdowns(jamTrack.mixdowns);
} }
//reset watched mixdowns on unload
return () => {
watchedMixdowns.forEach(subscription => {
window.JK.SubscriptionUtils.unsubscribe(subscription.type, subscription.id);
});
};
}, [jamTrack]); }, [jamTrack]);
useEffect(() => { useEffect(() => {
if (jamTrack) { if (newMixdownLoadingStatus === 'succeeded') {
if(newMixdownLoadingStatus === 'succeeded') { setValue('mixName', '');
setValue('mixName', ''); setValue('tempo', TEMPO_OPTIONS[0]);
setValue('tempo', TEMPO_OPTIONS[0]); setValue('pitch', PITCH_OPTIONS[0]);
setValue('pitch', PITCH_OPTIONS[0]); setValue('mixdownTracks', []);
setValue('mixdownTracks', []); setSelectedTracks([]);
setSelectedTracks([]);
setMixdowns(jamTrack.mixdowns);
}
} }
}, [newMixdownLoadingStatus]); }, [newMixdownLoadingStatus]);
useEffect(() => {
if (awaitingMixdown) {
//enqueue the mixdown
console.log('Enqueueing mixdown', awaitingMixdown);
const options = { id: awaitingMixdown.id, file_type: 'mp3', encrypt_type: null, sample_rate: SAMPLE_RATE };
dispatch(enqueueMyMixdown(options));
}
}, [awaitingMixdown]);
useEffect(() => {
if (enqueuedMixdown) {
showEstimatedTime();
manageWatchedMixdowns();
}
}, [enqueuedMixdown]);
const showEstimatedTime = () => {
console.log('Enqueued mixdown', enqueuedMixdown);
const time = enqueuedMixdown.queue_time;
if (time === 0) {
setEnqueueTimeMessage('Your custom mix will take about 1 minute to be created.');
} else {
const guess = Math.ceil(time / 60.0);
if (guess === 1) {
setEnqueueTimeMessage('Your custom mix will take about 1 minute to be created.');
} else {
setEnqueueTimeMessage(`Your custom mix will take about ${guess} minutes to be created.`);
}
}
setShowQueueTime(true);
};
const manageWatchedMixdowns = () => {
console.log('Managing watched mixdowns');
mixdowns.forEach(mixdown => {
if (mixdown.oggPackage) {
if (mixdown.oggPackage.signing_state === 'SIGNED') {
console.log('unsubscribing to mixdown', mixdown);
unsubscribe(mixdown.oggPackage);
} else {
console.log('subscribing to mixdown', mixdown);
subscribe(mixdown.oggPackage);
}
}
});
};
const subscriptionKey = mixdown_package => {
return `mixdown-${mixdown_package.id}`;
};
const subscribe = mixdown_package => {
const key = subscriptionKey(mixdown_package);
console.log('watchedMixdowns', watchedMixdowns);
if (!watchedMixdowns[key]) {
window.JK.SubscriptionUtils.subscribe('mixdown', mixdown_package.id).on(
window.JK.EVENTS.SUBSCRIBE_NOTIFICATION,
onMixdownSubscriptionEvent
);
dispatch(addWatchedMixdown({ type: 'mixdown', id: mixdown_package.id }));
}
};
const unsubscribe = mixdown_package => {
const key = subscriptionKey(mixdown_package);
if (watchedMixdowns[key]) {
window.JK.SubscriptionUtils.unsubscribe('mixdown', mixdown_package.id);
dispatch(removeWatchedMixdown({ type: 'mixdown', id: mixdown_package.id }));
}
};
const onMixdownSubscriptionEvent = (e, data) => {
console.log("JamTrackStore: subscription notification received: type:" + data.type, data)
const mixdown_package_id = data.id;
mixdowns.forEach(mixdown => {
const mixdown_package = mixdown.packages.find(p => p.id === mixdown_package_id);
if (mixdown_package) {
mixdown_package.signing_state = data.body.signing_state
mixdown_package.packaging_steps = data.body.packaging_steps
mixdown_package.current_packaging_step = data.body.current_packaging_step
console.log("updated package with subscription notification event")
if(mixdown_package.signing_state === 'SIGNING_TIMEOUT' || mixdown_package.signing_state === 'QUEUED_TIMEOUT' || mixdown_package.signing_state === 'QUIET_TIMEOUT' || mixdown_package.signing_state === 'ERROR'){
reportError(mixdown)
}
}
})
}
const reportError = (mixdown) => {
const enqueued = enqueuedMixdowns[mixdown?.id]
if (!enqueued || enqueued.marked) {
return
}
enqueued.marked = true
const data = {
value: 1,
user_id: currentUser.id,
user_name: currentUser.name,
result: `signing state: ${mixdown.oggPackage?.signing_state}, client state: ${mixdown.client_state}`,
mixdown: mixdown.id,
package: mixdown.oggPackage?.id,
detail: mixdown.oggPackage?.error_reason
}
createAlert(`Mixdown Sync failed for ${currentUser.name}`, data)
}
const trackName = track => { const trackName = track => {
if (track.track_type === 'Track' || track.track_type === 'Click') { if (track.track_type === 'Track' || track.track_type === 'Click') {
if (track.track_type === 'Click') { if (track.track_type === 'Click') {
@ -180,30 +311,34 @@ const JKCreateCustomMix = () => {
<form onSubmit={handleSubmit(onSubmit)}> <form onSubmit={handleSubmit(onSubmit)}>
<Row> <Row>
<Col> <Col>
<Scrollbar ref={scrollbar} style={{ width: '100%', height: 300 }} mobileNative={true}> <Scrollbar ref={scrollbar} style={{ width: '100%', height: 300 }} mobileNative={true}>
<Table striped bordered className="fs--1 mb-0"> <Table striped bordered className="fs--1 mb-0">
<thead className="bg-200 text-900"> <thead className="bg-200 text-900">
<tr> <tr>
<th>Tracks {tracks.length > 0 && <>({tracks.length})</>}</th> <th>Tracks {tracks.length > 0 && <>({tracks.length})</>}</th>
<th className="text-center">Mute</th> <th className="text-center">Mute</th>
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
{tracks &&
{tracks && tracks.map((track, index) => (
tracks.map((track, index) => ( <tr key={index}>
<tr key={index}> <td>
<td> <span>{trackName(track)}</span>
<span>{trackName(track)}</span> </td>
</td> <td className="text-center">
<td className="text-center"> <input
<input type="checkbox" value={track.id} onChange={toggleTrack} checked={ selectedTracks.includes(track.id)} disabled={hasExceededMax} /> type="checkbox"
</td> value={track.id}
</tr> onChange={toggleTrack}
))} checked={selectedTracks.includes(track.id)}
disabled={hasExceededMax}
</tbody> />
</Table> </td>
</tr>
))}
</tbody>
</Table>
</Scrollbar> </Scrollbar>
<Controller <Controller
name="mixdownTracks" name="mixdownTracks"
@ -211,7 +346,7 @@ const JKCreateCustomMix = () => {
rules={{ rules={{
required: 'Select at least one track to create a mix' required: 'Select at least one track to create a mix'
}} }}
render={({ field }) => <Input type='hidden' {...field} />} render={({ field }) => <Input type="hidden" {...field} />}
/> />
{errors.mixdownTracks && ( {errors.mixdownTracks && (
<div className="text-danger"> <div className="text-danger">
@ -222,7 +357,9 @@ const JKCreateCustomMix = () => {
</Row> </Row>
<Row className="mb-3 mt-3"> <Row className="mb-3 mt-3">
<Col sm={6} md={4} lg={3}>Tempo</Col> <Col sm={6} md={4} lg={3}>
Tempo
</Col>
<Col> <Col>
<Controller <Controller
name="tempo" name="tempo"
@ -232,7 +369,9 @@ const JKCreateCustomMix = () => {
</Col> </Col>
</Row> </Row>
<Row className="mb-3"> <Row className="mb-3">
<Col sm={6} md={4} lg={3}>Pitch</Col> <Col sm={6} md={4} lg={3}>
Pitch
</Col>
<Col> <Col>
<Controller <Controller
name="pitch" name="pitch"
@ -242,7 +381,9 @@ const JKCreateCustomMix = () => {
</Col> </Col>
</Row> </Row>
<Row className="mb-3"> <Row className="mb-3">
<Col sm={6} md={4} lg={3}>Mix Name</Col> <Col sm={6} md={4} lg={3}>
Mix Name
</Col>
<Col> <Col>
<Controller <Controller
name="mixName" name="mixName"
@ -260,13 +401,23 @@ const JKCreateCustomMix = () => {
</Col> </Col>
</Row> </Row>
<Row> <Row>
<Col className='d-flex justify-content-end'> <Col className="d-flex justify-content-end">
<Button color="primary" disabled={newMixdownLoadingStatus === 'loading' || hasExceededMax }> <Button color="primary" disabled={newMixdownLoadingStatus === 'loading' || hasExceededMax}>
{newMixdownLoadingStatus === 'loading' ? 'Creating Mix...' : 'Create Mix'} {newMixdownLoadingStatus === 'loading' ? 'Creating Mix...' : 'Create Mix'}
</Button> </Button>
</Col> </Col>
</Row> </Row>
</form> </form>
<JKModalDialog
show={showQueueTime}
onToggle={() => setShowQueueTime(!showQueueTime)}
title="Mixdown Queued"
showFooter={true}
>
<div className="d-flex flex-column">
<p>{enqueueTimeMessage}</p>
</div>
</JKModalDialog>
</> </>
); );
}; };

View File

@ -8,14 +8,13 @@ import { getUserDetail, postUserEvent, userOpenedJamTrackWebPlayer } from '../..
import JKJamTrackPlayer from './JKJamTrackPlayer'; import JKJamTrackPlayer from './JKJamTrackPlayer';
import JKMyJamTrackMixes from './JKMyJamTrackMixes'; import JKMyJamTrackMixes from './JKMyJamTrackMixes';
import JKCreateCustomMix from './JKCreateCustomMix'; import JKCreateCustomMix from './JKCreateCustomMix';
import JKJamTrackResourceLinks from './JKJamTrackResourceLinks';
import { useAuth } from '../../context/UserAuth'; import { useAuth } from '../../context/UserAuth';
import { fetchJamTrack } from '../../store/features/jamTrackSlice'; import { fetchJamTrack } from '../../store/features/jamTrackSlice';
import { useDispatch, useSelector } from 'react-redux'; import { useDispatch, useSelector } from 'react-redux';
const JKJamTrack = () => { const JKJamTrack = () => {
console.log('JKJamTrack rendering');
const { t } = useTranslation('jamtracks'); const { t } = useTranslation('jamtracks');
const { greaterThan } = useResponsive(); const { greaterThan } = useResponsive();
const { id } = useParams(); const { id } = useParams();
@ -25,7 +24,7 @@ const JKJamTrack = () => {
const dispatch = useDispatch(); const dispatch = useDispatch();
const jamTrack = useSelector(state => state.jamTrack.jamTrack); const jamTrack = useSelector(state => state.jamTrack.jamTrack);
const jamTrackLoadingStatus = useSelector(state => state.jamTrack.status); const jamTrackLoadingStatus = useSelector(state => state.jamTrack.jamTrackLoadingStatus);
const fetchJamTrackRecord = () => { const fetchJamTrackRecord = () => {
dispatch(fetchJamTrack({ id })); dispatch(fetchJamTrack({ id }));
@ -62,7 +61,7 @@ const JKJamTrack = () => {
<> <>
{jamTrackLoadingStatus === 'loading' || jamTrackLoadingStatus == 'idel' ? ( {jamTrackLoadingStatus === 'loading' || jamTrackLoadingStatus == 'idel' ? (
<div>Loading...</div> <div>Loading...</div>
) : Object.keys(jamTrack).length ? ( ) : jamTrack ? (
<Row> <Row>
<Col sm={12} md={4}> <Col sm={12} md={4}>
<Card className="mx-auto mb-4"> <Card className="mx-auto mb-4">
@ -84,41 +83,7 @@ const JKJamTrack = () => {
<Card className="mx-auto"> <Card className="mx-auto">
<FalconCardHeader title={t('jamtrack.help_resources.title')} titleClass="font-weight-semi-bold" /> <FalconCardHeader title={t('jamtrack.help_resources.title')} titleClass="font-weight-semi-bold" />
<CardBody className="pt-3"> <CardBody className="pt-3">
<div className="mb-3"> <JKJamTrackResourceLinks jamTrack={jamTrack} />
<div>
<a target="_blank" href="https://jamkazam.freshdesk.com/support/solutions/articles/66000501472">
{t('jamtrack.help_resources.using_overview.title')}
</a>
</div>
{t('jamtrack.help_resources.using_overview.description')}
</div>
<div className="mb-3">
<div>
<a target="_blank" href="https://jamkazam.freshdesk.com/support/solutions/articles/66000125792">
{t('jamtrack.help_resources.using_mac_windows.title')}
</a>
</div>
{t('jamtrack.help_resources.using_mac_windows.description')}
</div>
<div className="mb-3">
<div>
<a target="_blank" href="https://www.jamkazam.com/client#/jamtrack">
{t('jamtrack.help_resources.jamtracks_home.title')}
</a>
</div>
{t('jamtrack.help_resources.jamtracks_home.description')}
</div>
<div className="mb-3">
<div>
<a target="_blank" href={`https://www.jamkazam.com/client?artist=${encodeURIComponent(
jamTrack.original_artist
)}#/jamtrack/search`}
>
{t('jamtrack.help_resources.see_more.title')}
</a>
</div>
{t('jamtrack.help_resources.see_more.description')}
</div>
</CardBody> </CardBody>
</Card> </Card>
</Col> </Col>

View File

@ -5,7 +5,7 @@ import FingerprintJS from '@fingerprintjs/fingerprintjs';
import { useSelector, useDispatch } from 'react-redux'; import { useSelector, useDispatch } from 'react-redux';
import JKModalDialog from '../common/JKModalDialog'; import JKModalDialog from '../common/JKModalDialog';
import { enqueueMyMixdown } from '../../store/features/jamTrackSlice'; import { enqueueMyMixdown } from '../../store/features/jamTrackSlice';
import { set } from 'react-hook-form'; import { SAMPLE_RATE } from '../../helpers/jamTracks';
const JKJamTrackPlayer = () => { const JKJamTrackPlayer = () => {
const [options, setOptions] = useState([]); const [options, setOptions] = useState([]);
@ -15,7 +15,6 @@ const JKJamTrackPlayer = () => {
const audioRef = useRef(null); const audioRef = useRef(null);
const jamTrack = useSelector(state => state.jamTrack.jamTrack); const jamTrack = useSelector(state => state.jamTrack.jamTrack);
const mixdownsLoadingStatus = useSelector(state => state.jamTrack.mixdownsLoadingStatus); const mixdownsLoadingStatus = useSelector(state => state.jamTrack.mixdownsLoadingStatus);
const SAMPLE_RATE = 48;
const [retryMessage, setRetryMessage] = useState(''); const [retryMessage, setRetryMessage] = useState('');
const [showRetry, setShowRetry] = useState(false); const [showRetry, setShowRetry] = useState(false);
const [retryDownload, setRetryDownload] = useState(false); const [retryDownload, setRetryDownload] = useState(false);
@ -24,7 +23,7 @@ const JKJamTrackPlayer = () => {
useEffect(() => { useEffect(() => {
if (jamTrack) { if (jamTrack) {
const opts = jamTrack.mixdowns.filter(mix => mix.id !== 'temp').map(mix => ({ value: mix.id, label: mix.name })); const opts = jamTrack.mixdowns.map(mix => ({ value: mix.id, label: mix.name }));
opts.unshift({ value: 'original', label: 'Original' }); opts.unshift({ value: 'original', label: 'Original' });
setOptions(opts); setOptions(opts);
if (jamTrack.last_mixdown_id) { if (jamTrack.last_mixdown_id) {
@ -55,7 +54,7 @@ const JKJamTrackPlayer = () => {
return; return;
} }
console.log('selectedOption', selectedOption);
if (selectedOption.value === 'original') { if (selectedOption.value === 'original') {
const audioUrl = getOriginalTrackUrl(); const audioUrl = getOriginalTrackUrl();
setAudioUrl(audioUrl); setAudioUrl(audioUrl);
@ -64,19 +63,17 @@ const JKJamTrackPlayer = () => {
} }
} else { } else {
//it's a mixdown //it's a mixdown
//see if there is a myPackage avaialble. if it is, it means the mixdown is ready console.log('_selectedOption', jamTrack);
//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; let retry = false;
if (myPackage) { if (jamTrack.mp3Package && jamTrack.mp3Package.signing_state) {
//let see if the mixdown is signed // SIGNED - the package is ready to be downloaded
switch (myPackage.signing_state) { // ERROR - the package was built unsuccessfully
// SIGNING_TIMEOUT - the package was kicked off to be signed, but it seems to have hung
// SIGNING - the package is currently signing
// QUEUED_TIMEOUT - the package signing job (JamTrackBuilder) was queued, but never executed
// QUEUED - the package is queued to sign
// QUIET - the jam_track_right exists, but no job has been kicked off; a job needs to be enqueued
switch (jamTrack.mp3Package.signing_state) {
case 'QUIET_TIMEOUT': case 'QUIET_TIMEOUT':
setRetryMessage('Custom mix never got created. Retry?'); setRetryMessage('Custom mix never got created. Retry?');
retry = true; retry = true;
@ -86,13 +83,17 @@ const JKJamTrackPlayer = () => {
retry = true; retry = true;
break; break;
case 'SIGNING_TIMEOUT': case 'SIGNING_TIMEOUT':
setRetryMessage('Custom mix took took long to build. Retry?'); setRetryMessage('Custom mix took long to build. Retry?');
retry = true; retry = true;
break; break;
case 'ERROR': case 'ERROR':
setRetryMessage('Custom mix failed to build. Retry?'); setRetryMessage('Custom mix failed to build. Retry?');
retry = true; retry = true;
break; break;
case 'QUIET':
setRetryMessage('Custom mix never got created. Retry?');
retry = true;
break;
default: default:
break; break;
} }
@ -102,6 +103,7 @@ const JKJamTrackPlayer = () => {
setAudioUrl(null); setAudioUrl(null);
} }
if (retry) { if (retry) {
setShowRetry(true); setShowRetry(true);
return; return;
@ -120,6 +122,7 @@ const JKJamTrackPlayer = () => {
useEffect(() => { useEffect(() => {
if (retryDownload && selectedOption) { if (retryDownload && selectedOption) {
const options = { id: selectedOption.value, file_type: 'mp3', encrypt_type: null, sample_rate: SAMPLE_RATE }; const options = { id: selectedOption.value, file_type: 'mp3', encrypt_type: null, sample_rate: SAMPLE_RATE };
console.log('enqueueMyMixdown', options);
dispatch(enqueueMyMixdown(options)); dispatch(enqueueMyMixdown(options));
} }
}, [retryDownload]); }, [retryDownload]);

View File

@ -0,0 +1,49 @@
import React from 'react';
import { useTranslation } from 'react-i18next';
const JKJamTrackResourceLinks = ({jamTrack}) => {
const { t } = useTranslation('jamtracks');
return (
<>
<div className="mb-3">
<div>
<a target="_blank" href="https://jamkazam.freshdesk.com/support/solutions/articles/66000501472">
{t('jamtrack.help_resources.using_overview.title')}
</a>
</div>
{t('jamtrack.help_resources.using_overview.description')}
</div>
<div className="mb-3">
<div>
<a target="_blank" href="https://jamkazam.freshdesk.com/support/solutions/articles/66000125792">
{t('jamtrack.help_resources.using_mac_windows.title')}
</a>
</div>
{t('jamtrack.help_resources.using_mac_windows.description')}
</div>
<div className="mb-3">
<div>
<a target="_blank" href="https://www.jamkazam.com/client#/jamtrack">
{t('jamtrack.help_resources.jamtracks_home.title')}
</a>
</div>
{t('jamtrack.help_resources.jamtracks_home.description')}
</div>
<div className="mb-3">
<div>
<a
target="_blank"
href={`https://www.jamkazam.com/client?artist=${encodeURIComponent(
jamTrack.original_artist
)}#/jamtrack/search`}
>
{t('jamtrack.help_resources.see_more.title')}
</a>
</div>
{t('jamtrack.help_resources.see_more.description')}
</div>
</>
);
};
export default JKJamTrackResourceLinks;

View File

@ -6,30 +6,13 @@ import { useDispatch, useSelector } from 'react-redux';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
const JKMyJamTrackMixes = () => { const JKMyJamTrackMixes = () => {
const [mixes, setMixes] = useState([]);
const fpPromise = FingerprintJS.load(); const fpPromise = FingerprintJS.load();
const dispatch = useDispatch(); const dispatch = useDispatch();
const jamTrack = useSelector(state => state.jamTrack.jamTrack); const jamTrack = useSelector(state => state.jamTrack.jamTrack);
const mixdowns = useSelector(state => state.jamTrack.mixdowns);
const mixdownsLoadingStatus = useSelector(state => state.jamTrack.mixdownsLoadingStatus); const mixdownsLoadingStatus = useSelector(state => state.jamTrack.mixdownsLoadingStatus);
const deleteMixdownStatus = useSelector(state => state.jamTrack.deleteMixdownStatus); const deleteMixdownStatus = useSelector(state => state.jamTrack.deleteMixdownStatus);
const tempMixdownLoadingStatus = useSelector(state => state.jamTrack.tempMixdownLoadingStatus);
const buildRetries = useSelector(state => state.jamTrack.buildRetries);
useEffect(() => {
if (!jamTrack) {
return;
}
if (mixdownsLoadingStatus === 'succeeded') {
setMixes(jamTrack.mixdowns.filter(m => m.id !== 'temp'));
}
}, [mixdownsLoadingStatus]);
useEffect(() => {
if (tempMixdownLoadingStatus === 'succeeded') {
setMixes(jamTrack.mixdowns);
}
}, [tempMixdownLoadingStatus]);
const downloadJamTrack = async () => { const downloadJamTrack = async () => {
console.log('Downloading JamTrack'); console.log('Downloading JamTrack');
@ -47,7 +30,7 @@ const JKMyJamTrackMixes = () => {
const downloadMix = async mixId => { const downloadMix = async mixId => {
console.log('Download mixdown'); console.log('Download mixdown');
const mixdown = mixes.find(m => m.id === mixId); const mixdown = mixdowns.find(m => m.id === mixId);
const mixdownPackage = mixdown.packages.find(p => p.file_type === 'mp3'); const mixdownPackage = mixdown.packages.find(p => p.file_type === 'mp3');
if (mixdownPackage?.signing_state == 'SIGNED') { if (mixdownPackage?.signing_state == 'SIGNED') {
const fp = await fpPromise; const fp = await fpPromise;
@ -56,7 +39,7 @@ const JKMyJamTrackMixes = () => {
mixdown.id mixdown.id
}/download.mp3?file_type=mp3&sample_rate=48&download=1&mark=${result.visitorId}`; }/download.mp3?file_type=mp3&sample_rate=48&download=1&mark=${result.visitorId}`;
openDownload(src); openDownload(src);
}else{ } else {
console.log('Mixdown not signed'); console.log('Mixdown not signed');
} }
}; };
@ -75,10 +58,17 @@ const JKMyJamTrackMixes = () => {
} }
}; };
const isMixdownBuilding = mix => { const mixdownPackage = mixdown => {
const retry = buildRetries.find(retry => retry.mixdownId === mix.id); if (!mixdown.packages || mixdown.packages.length === 0) {
return mix.id === 'temp' || (retry && ['queued', 'enqueuing'].includes(retry.state)) return null;
} }
return mixdown.packages[0];
};
const isMixdownPackageReady = mixdown => {
const pkg = mixdownPackage(mixdown);
return pkg && pkg.signing_state === 'SIGNED';
};
return ( return (
<> <>
@ -102,25 +92,36 @@ const JKMyJamTrackMixes = () => {
</a> </a>
</td> </td>
</tr> </tr>
{mixes.map(mix => ( {mixdowns.map(mixdown => (
<tr key={mix.id}> <tr key={mixdown.id}>
<td>{mix.name}</td> <td>
{mixdown.name} <br />
{mixdown.id} <br />
pkg-{mixdownPackage(mixdown)?.signing_state}
</td>
<td className="text-center"> <td className="text-center">
{isMixdownBuilding(mix) ? (
<FontAwesomeIcon icon="spinner" size="lg" /> {isMixdownPackageReady(mixdown) ? (
) : (
<> <>
<a onClick={() => downloadMix(mix.id)} style={{ cursor: 'pointer' }}> <a onClick={() => downloadMix(mixdown.id)} style={{ cursor: 'pointer' }}>
<FontAwesomeIcon icon="download" size="lg" className="mr-3" /> <FontAwesomeIcon icon="download" size="lg" className="mr-3" />
</a> </a>
<a <a
onClick={() => deleteMix(mix.id)} onClick={() => deleteMix(mixdown.id)}
disabled={deleteMixdownStatus === 'loading'} disabled={deleteMixdownStatus === 'loading'}
style={{ cursor: 'pointer' }} style={{ cursor: 'pointer' }}
> >
<FontAwesomeIcon icon="trash" size="lg" /> <FontAwesomeIcon icon="trash" size="lg" />
</a> </a>
</> </>
) : (
<>
{mixdown.signing_state === 'QUEUED' || mixdown.signing_state === 'SIGNING' ? (
<FontAwesomeIcon icon="spinner" size="lg" className="mr-3" />
) : (
<FontAwesomeIcon icon="exclamation-circle" size="lg" className="mr-3" />
)}
</>
)} )}
</td> </td>
</tr> </tr>

View File

@ -0,0 +1 @@
export const SAMPLE_RATE = 48;

View File

@ -1,4 +1,3 @@
import { error } from 'is_js';
import apiFetch from './apiFetch'; import apiFetch from './apiFetch';
export const getMusicians = page => { export const getMusicians = page => {
@ -623,3 +622,14 @@ export const deleteAvatar = id => {
.catch(error => reject(error)); .catch(error => reject(error));
}); });
} }
export const createAlert = (subject, data) => {
return new Promise((resolve, reject) => {
apiFetch(`/alerts`, {
method: 'POST',
body: JSON.stringify({subject, data})
})
.then(response => resolve(response))
.catch(error => reject(error));
});
};

View File

@ -1,15 +1,19 @@
import { createSlice, createAsyncThunk } from '@reduxjs/toolkit'; import { createSlice, createAsyncThunk } from '@reduxjs/toolkit';
import { deleteMixdown, getJamTrack, createMixdown, enqueueMixdown } from '../../helpers/rest'; import { deleteMixdown, getJamTrack, createMixdown, enqueueMixdown } from '../../helpers/rest';
import { SAMPLE_RATE } from '../../helpers/jamTracks';
const initialState = { const initialState = {
jamTrack: {}, jamTrack: null,
mixdowns: [],
awaitingMixdown: null,
enqueuedMixdown: null,
enqueuedMixdowns: [],
watchedMixdowns: [],
jamTrackLoadingStatus: 'idle', jamTrackLoadingStatus: 'idle',
mixdownsLoadingStatus: 'idle', mixdownsLoadingStatus: 'idle',
deleteMixdownStatus: 'idle', deleteMixdownStatus: 'idle',
newMixdownLoadingStatus: 'idle', newMixdownLoadingStatus: 'idle',
tempMixdownLoadingStatus: 'idle', tempMixdownLoadingStatus: 'idle',
enqueueLoadingStatus: 'idle',
buildRetries: [],
error: null error: null
}; };
@ -24,7 +28,6 @@ export const createMyMixdown = createAsyncThunk('jamTracks/createMixdown', async
}); });
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 { id } = options;
const response = await deleteMixdown(id); const response = await deleteMixdown(id);
return { id }; return { id };
@ -35,6 +38,10 @@ export const enqueueMyMixdown = createAsyncThunk('jamTracks/enqueueMixdown', asy
return response.json(); return response.json();
}); });
export const jamTrackSlice = createSlice({ export const jamTrackSlice = createSlice({
name: 'jamTrack', name: 'jamTrack',
initialState, initialState,
@ -46,6 +53,26 @@ export const jamTrackSlice = createSlice({
state.jamTrack.mixdowns = [...jamTrack.mixdowns, payload]; state.jamTrack.mixdowns = [...jamTrack.mixdowns, payload];
state.tempMixdownLoadingStatus = 'succeeded'; state.tempMixdownLoadingStatus = 'succeeded';
} }
},
addWatchedMixdown: (state, action) => {
const payload = action.payload;
state.watchedMixdowns = [...state.watchedMixdowns, payload];
},
removeWatchedMixdown: (state, action) => {
const payload = action.payload;
state.watchedMixdowns = state.watchedMixdowns.filter(mix => mix.id !== payload.id);
},
updateMixdown: (state, action) => {
const payload = action.payload;
const jamTrack = state.jamTrack;
if (jamTrack) {
state.jamTrack.mixdowns = jamTrack.mixdowns.map(mix => {
if (mix.id === payload.id) {
return payload;
}
return mix;
});
}
} }
}, },
extraReducers: builder => { extraReducers: builder => {
@ -57,23 +84,26 @@ export const jamTrackSlice = createSlice({
.addCase(fetchJamTrack.fulfilled, (state, action) => { .addCase(fetchJamTrack.fulfilled, (state, action) => {
state.jamTrack = action.payload; state.jamTrack = action.payload;
state.jamTrackLoadingStatus = 'succeeded'; state.jamTrackLoadingStatus = 'succeeded';
if (action.payload.mixdowns) { if (action.payload.mixdowns) {
state.mixdowns = action.payload.mixdowns;
assignPackages(state);
state.mixdownsLoadingStatus = 'succeeded'; state.mixdownsLoadingStatus = 'succeeded';
} }
}) })
.addCase(fetchJamTrack.rejected, (state, action) => { .addCase(fetchJamTrack.rejected, (state, action) => {
state.status = 'failed';
state.jamTrackLoadingStatus = 'failed'; state.jamTrackLoadingStatus = 'failed';
state.jamTrack = null;
state.mixdownsLoadingStatus = 'failed'; state.mixdownsLoadingStatus = 'failed';
state.mixdowns = [];
state.error = action.error.message; state.error = action.error.message;
}) })
.addCase(createMyMixdown.pending, (state, action) => { .addCase(createMyMixdown.pending, (state, action) => {
state.newMixdownLoadingStatus = 'loading'; state.newMixdownLoadingStatus = 'loading';
state.mixdownsLoadingStatus = 'loading'; state.tempMixdownLoadingStatus = 'loading';
}) })
.addCase(createMyMixdown.fulfilled, (state, action) => { .addCase(createMyMixdown.fulfilled, (state, action) => {
state.jamTrack.mixdowns = [...state.jamTrack.mixdowns, action.payload]; state.mixdowns = [...state.mixdowns, action.payload];
state.awaitingMixdown = action.payload;
state.newMixdownLoadingStatus = 'succeeded'; state.newMixdownLoadingStatus = 'succeeded';
state.mixdownsLoadingStatus = 'succeeded'; state.mixdownsLoadingStatus = 'succeeded';
state.tempMixdownLoadingStatus = 'idle'; state.tempMixdownLoadingStatus = 'idle';
@ -88,9 +118,7 @@ export const jamTrackSlice = createSlice({
state.deleteMixdownStatus = 'loading'; state.deleteMixdownStatus = 'loading';
}) })
.addCase(removeMixdown.fulfilled, (state, action) => { .addCase(removeMixdown.fulfilled, (state, action) => {
console.log('mixdown removed', action.payload); const mixdowns = state.mixdowns.filter(mix => mix.id !== action.payload.id);
const mixdowns = state.jamTrack.mixdowns.filter(mix => mix.id !== action.payload.id);
state.jamTrack.mixdowns = mixdowns;
state.mixdowns = mixdowns; state.mixdowns = mixdowns;
state.mixdownsLoadingStatus = 'succeeded'; state.mixdownsLoadingStatus = 'succeeded';
state.deleteMixdownStatus = 'succeeded'; state.deleteMixdownStatus = 'succeeded';
@ -100,47 +128,36 @@ export const jamTrackSlice = createSlice({
state.mixdownsLoadingStatus = 'failed'; state.mixdownsLoadingStatus = 'failed';
state.deleteMixdownStatus = '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) => { .addCase(enqueueMyMixdown.fulfilled, (state, action) => {
const estimate = { mixdownId: action.payload.id, time: action.payload.queue_time, state: 'queued' }; console.log('enqueueMyMixdown.fulfilled', action.payload);
if (!state.buildRetries.find(estimate => estimate.mixdownId === action.payload.id)) { const enqueue = { queue_time: action.payload.queue_time, signing_state: action.payload.signing_state };
state.buildRetries = [...state.buildRetries, estimate]; state.enqueuedMixdown = enqueue;
} else { state.enqueuedMixdowns = [...state.enqueuedMixdowns, enqueue];
state.buildRetries = state.buildRetries.map(estimate => { state.mixdowns = state.mixdowns.map(mix => {
if (estimate.mixdownId === action.payload.id) { if (mix.id === action.payload.jam_track_mixdown_id) {
return { ...estimate, ...action.payload, state: 'queued' }; return { ...mix, ...enqueue };
} }
return estimate; return mix;
}); });
} assignPackages(state);
}) })
.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; //help functions
const pickMp3Package = (mixdown) => {
return mixdown.packages.find(p => p.file_type === 'mp3' && p.encrypt_type === null && p.sample_rate === SAMPLE_RATE);
};
const pickOggPackage = (mixdown) => {
return mixdown.packages.find(p => p.file_type === 'ogg' && p.encrypt_type === null && p.sample_rate === SAMPLE_RATE);
};
const assignPackages = (state) => {
state.jamTrack.mp3Package = state.mixdowns.map(pickMp3Package).filter(p => p);
state.jamTrack.oggPackage = state.mixdowns.map(pickOggPackage).filter(p => p);
};
export const { addMixdown, addWatchedMixdown, removeWatchedMixdown } = jamTrackSlice.actions;
export default jamTrackSlice.reducer; export default jamTrackSlice.reducer;

View File

@ -627,6 +627,7 @@
} }
server.registerMessageCallback = function (messageType, callback) { server.registerMessageCallback = function (messageType, callback) {
console.log("_DEBUG_ jamserver server.registerMessageCallback", messageType, callback)
if (server.dispatchTable[messageType] === undefined) { if (server.dispatchTable[messageType] === undefined) {
server.dispatchTable[messageType] = []; server.dispatchTable[messageType] = [];
} }
@ -818,6 +819,8 @@
var jsMessage = JSON.stringify(message); var jsMessage = JSON.stringify(message);
console.log("server.send(" + jsMessage + ")");
if( isLatencyTester() && (message.type == context.JK.MessageType.HEARTBEAT || message.type == context.JK.MessageType.PEER_MESSAGE)) { if( isLatencyTester() && (message.type == context.JK.MessageType.HEARTBEAT || message.type == context.JK.MessageType.PEER_MESSAGE)) {
logger.info("latency-tester: server.send(" + jsMessage + ")") logger.info("latency-tester: server.send(" + jsMessage + ")")
} }

View File

@ -607,6 +607,7 @@
} }
server.registerMessageCallback = function (messageType, callback) { server.registerMessageCallback = function (messageType, callback) {
console.log("_DEBUG_ jamserver_copy server.registerMessageCallback", messageType, callback)
if (server.dispatchTable[messageType] === undefined) { if (server.dispatchTable[messageType] === undefined) {
server.dispatchTable[messageType] = []; server.dispatchTable[messageType] = [];
} }

View File

@ -72,6 +72,7 @@ class SubscriptionUtils
$server.on(@events.CONNECTION_DOWN, this.onConnectionDown) $server.on(@events.CONNECTION_DOWN, this.onConnectionDown)
onSubscriptionMessage: (header, payload) => onSubscriptionMessage: (header, payload) =>
console.log("onSubscriptionMessage", payload, header)
key = this.genKey(payload.type, payload.id) key = this.genKey(payload.type, payload.id)
watch = @subscriptions[key] watch = @subscriptions[key]

View File

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