JamTracks related ui fixes

This commit is contained in:
Nuwan 2024-12-22 02:11:08 +05:30
parent c3563a9197
commit 48547d2cb1
10 changed files with 576 additions and 338 deletions

View File

@ -1,31 +1,18 @@
import React, { useState, useEffect, useRef } from 'react';
import { Table, Row, Col, Input, Button } from 'reactstrap';
import Select from 'react-select';
import { useForm, Controller, set } from 'react-hook-form';
import {
createMyMixdown,
enqueueMyMixdown,
addWatchedMixdown,
removeWatchedMixdown
} from '../../store/features/jamTrackSlice';
import { useDispatch, useSelector } from 'react-redux';
import { useForm, Controller } from 'react-hook-form';
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';
import { useJamTrack } from '../../hooks/useJamTrack';
import JKSigningEstimateTimeModal from './JKSigningEstimateTimeModal';
const JKCreateCustomMix = () => {
const MAX_MIXDOWNS = 5;
const [tracks, setTracks] = useState([]);
const [selectedTracks, setSelectedTracks] = useState([]);
const [showQueueTime, setShowQueueTime] = useState(false);
const [enqueueTimeMessage, setEnqueueTimeMessage] = useState('');
const dispatch = useDispatch();
const scrollbar = useRef();
const { currentUser} = useAuth();
const TEMPO_OPTIONS = [
{ value: '0', label: 'Original tempo' },
{ value: '-5', label: 'Slower by 5%' },
@ -49,7 +36,6 @@ const JKCreateCustomMix = () => {
{ value: '40', label: 'Faster by 40%' },
{ value: '50', label: 'Faster by 50%' }
];
const PITCH_OPTIONS = [
{ value: '0', label: 'Original pitch' },
{ value: '-1', label: 'Down 1 semitone' },
@ -78,13 +64,7 @@ const JKCreateCustomMix = () => {
{ value: '12', label: 'Up 12 semitone' }
];
const jamTrack = useSelector(state => state.jamTrack.jamTrack);
const mixdowns = useSelector(state => state.jamTrack.mixdowns);
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 { jamTrack, mixdowns, createMixdown, newMixdownLoadingStatus, awaitingMixdown, enqueueMixdownForSigning, enqueuedMixdown, showQueueTime, setShowQueueTime, enqueueTimeMessage, showEstimatedTime, manageWatchedMixdowns, trackName } = useJamTrack();
const {
control,
@ -141,7 +121,8 @@ const JKCreateCustomMix = () => {
//const tempMixdown = { ...mixData, id: 'temp', jam_track_id: jamTrack.id };
//dispatch(addMixdown(tempMixdown));
dispatch(createMyMixdown(mixData));
// dispatch(createMyMixdown(mixData));
createMixdown(mixData);
};
const toggleTrack = e => {
@ -159,12 +140,7 @@ const JKCreateCustomMix = () => {
if (jamTrack) {
setTracks(jamTrack.tracks.filter(track => track.track_type === 'Track' || track.track_type === 'Click'));
}
//reset watched mixdowns on unload
return () => {
watchedMixdowns.forEach(subscription => {
window.JK.SubscriptionUtils.unsubscribe(subscription.type, subscription.id);
});
};
}, [jamTrack]);
useEffect(() => {
@ -177,128 +153,45 @@ const JKCreateCustomMix = () => {
}
}, [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));
console.log('*_DEBUG_ awaitingMixdown', awaitingMixdown);
const options = {
id: awaitingMixdown.id,
file_type: 'mp3',
encrypt_type: null,
sample_rate: SAMPLE_RATE,
origin: 'create'
};
enqueueMixdownForSigning(options);
}
}, [awaitingMixdown]);
useEffect(() => {
if (enqueuedMixdown) {
if (enqueuedMixdown && enqueuedMixdown.origin === 'create') {
console.log('*_DEBUG_ enqueuedMixdown from create', 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 => {
if (track.track_type === 'Track' || track.track_type === 'Click') {
if (track.track_type === 'Click') {
return 'Clicktrack';
} else if (track.instrument) {
const instrumentDescription = track.instrument.description;
let part = '';
if (track.part && track.part !== instrumentDescription) {
part = `(${track.part})`;
}
return `${instrumentDescription} ${part}`;
}
}
};
// const trackName = track => {
// if (track.track_type === 'Track' || track.track_type === 'Click') {
// if (track.track_type === 'Click') {
// return 'Clicktrack';
// } else if (track.instrument) {
// const instrumentDescription = track.instrument.description;
// let part = '';
// if (track.part && track.part !== instrumentDescription) {
// part = `(${track.part})`;
// }
// return `${instrumentDescription} ${part}`;
// }
// }
// };
const hasExceededMax = mixdowns.length >= MAX_MIXDOWNS;
@ -408,16 +301,12 @@ const JKCreateCustomMix = () => {
</Col>
</Row>
</form>
<JKModalDialog
show={showQueueTime}
onToggle={() => setShowQueueTime(!showQueueTime)}
title="Mixdown Queued"
showFooter={true}
>
<div className="d-flex flex-column">
<p>{enqueueTimeMessage}</p>
</div>
</JKModalDialog>
<JKSigningEstimateTimeModal
showQueueTime={showQueueTime}
setShowQueueTime={setShowQueueTime}
enqueueTimeMessage={enqueueTimeMessage}
/>
</>
);
};

View File

@ -1,7 +1,7 @@
import React, { useEffect } from 'react';
import { useTranslation } from 'react-i18next';
import { useResponsive } from '@farfetch/react-context-responsive';
import { useParams } from 'react-router-dom';
import { useParams, Link } from 'react-router-dom';
import { Card, CardBody, Row, Col } from 'reactstrap';
import FalconCardHeader from '../common/FalconCardHeader';
import { getUserDetail, postUserEvent, userOpenedJamTrackWebPlayer } from '../../helpers/rest';
@ -10,26 +10,15 @@ import JKMyJamTrackMixes from './JKMyJamTrackMixes';
import JKCreateCustomMix from './JKCreateCustomMix';
import JKJamTrackResourceLinks from './JKJamTrackResourceLinks';
import { useAuth } from '../../context/UserAuth';
import { fetchJamTrack } from '../../store/features/jamTrackSlice';
import { useDispatch, useSelector } from 'react-redux';
import { useJamTrack } from '../../hooks/useJamTrack';
const JKJamTrack = () => {
const { t } = useTranslation('jamtracks');
const { greaterThan } = useResponsive();
const { id } = useParams();
const { currentUser } = useAuth();
const dispatch = useDispatch();
const jamTrack = useSelector(state => state.jamTrack.jamTrack);
const jamTrackLoadingStatus = useSelector(state => state.jamTrack.jamTrackLoadingStatus);
const fetchJamTrackRecord = () => {
dispatch(fetchJamTrack({ id }));
};
const { loadJamTrack, jamTrack, jamTrackLoadingStatus } = useJamTrack();
const fetchUserDetail = async () => {
try {
const userId = currentUser.id;
@ -54,7 +43,7 @@ const JKJamTrack = () => {
}, [currentUser, jamTrack]);
useEffect(() => {
fetchJamTrackRecord();
loadJamTrack(id);
}, [id]);
return (
@ -62,32 +51,39 @@ const JKJamTrack = () => {
{jamTrackLoadingStatus === 'loading' || jamTrackLoadingStatus == 'idel' ? (
<div>Loading...</div>
) : jamTrack ? (
<Row>
<Col sm={12} md={4}>
<Card className="mx-auto mb-4">
<FalconCardHeader title={t('jamtrack.player.title')} titleClass="font-weight-semi-bold" />
<CardBody className="pt-3">{jamTrack && <JKJamTrackPlayer />}</CardBody>
</Card>
<Card className="mx-auto">
<FalconCardHeader title={t('jamtrack.my_mixes.title')} titleClass="font-weight-semi-bold" />
<CardBody className="pt-3">{jamTrack && <JKMyJamTrackMixes />}</CardBody>
</Card>
</Col>
<Col sm={12} md={4}>
<Card className="mx-auto">
<FalconCardHeader title={t('jamtrack.create_mix.title')} titleClass="font-weight-semi-bold" />
<CardBody className="pt-3">{jamTrack && <JKCreateCustomMix />}</CardBody>
</Card>
</Col>
<Col sm={12} md={4}>
<Card className="mx-auto">
<FalconCardHeader title={t('jamtrack.help_resources.title')} titleClass="font-weight-semi-bold" />
<CardBody className="pt-3">
<JKJamTrackResourceLinks jamTrack={jamTrack} />
</CardBody>
</Card>
</Col>
</Row>
<>
<Row className='mb-3'>
<Col>
<Link to="/my-jamtracks">{`< Back to My JamTracks`}</Link>
</Col>
</Row>
<Row>
<Col sm={12} md={4}>
<Card className="mx-auto mb-4">
<FalconCardHeader title={t('jamtrack.player.title')} titleClass="font-weight-semi-bold" />
<CardBody className="pt-3">{jamTrack && <JKJamTrackPlayer />}</CardBody>
</Card>
<Card className="mx-auto">
<FalconCardHeader title={t('jamtrack.my_mixes.title')} titleClass="font-weight-semi-bold" />
<CardBody className="pt-3">{jamTrack && <JKMyJamTrackMixes />}</CardBody>
</Card>
</Col>
<Col sm={12} md={4}>
<Card className="mx-auto">
<FalconCardHeader title={t('jamtrack.create_mix.title')} titleClass="font-weight-semi-bold" />
<CardBody className="pt-3">{jamTrack && <JKCreateCustomMix />}</CardBody>
</Card>
</Col>
<Col sm={12} md={4}>
<Card className="mx-auto">
<FalconCardHeader title={t('jamtrack.help_resources.title')} titleClass="font-weight-semi-bold" />
<CardBody className="pt-3">
<JKJamTrackResourceLinks jamTrack={jamTrack} />
</CardBody>
</Card>
</Col>
</Row>
</>
) : null}
</>
);

View File

@ -1,38 +1,43 @@
import React, { useState, useEffect, useRef } from 'react';
import Select from 'react-select';
import { Row, Col } from 'reactstrap';
import FingerprintJS from '@fingerprintjs/fingerprintjs';
import { useSelector, useDispatch } from 'react-redux';
import JKModalDialog from '../common/JKModalDialog';
import { enqueueMyMixdown } from '../../store/features/jamTrackSlice';
import { SAMPLE_RATE } from '../../helpers/jamTracks';
import { useJamTrack } from '../../hooks/useJamTrack';
import JKSigningRetryConfirmModal from './JKSigningRetryConfirmModal';
import JKSigningEstimateTimeModal from './JKSigningEstimateTimeModal';
import { markMixdownActive } from '../../helpers/rest';
const JKJamTrackPlayer = () => {
const [options, setOptions] = useState([]);
const [selectedOption, setSelectedOption] = useState(null);
const fpPromise = FingerprintJS.load();
const [audioUrl, setAudioUrl] = useState(null);
const audioRef = useRef(null);
const jamTrack = useSelector(state => state.jamTrack.jamTrack);
const mixdownsLoadingStatus = useSelector(state => state.jamTrack.mixdownsLoadingStatus);
const [retryMessage, setRetryMessage] = useState('');
const [showRetry, setShowRetry] = useState(false);
const [retryDownload, setRetryDownload] = useState(false);
const dispatch = useDispatch();
const { jamTrack, mixdowns, getMasterAudioUrl, getMixdownAudioUrl, confirmRetryMixdownSigning, showQueueTime, setShowQueueTime, enqueueTimeMessage, retryConfirmMessage, showRetryConfirm, setShowRetryConfirm, enqueueMixdownForSigning, enqueuedMixdown, showEstimatedTime, manageWatchedMixdowns, getStemAudioUrl, trackName} = useJamTrack();
useEffect(() => {
if (jamTrack) {
const opts = jamTrack.mixdowns.map(mix => ({ value: mix.id, label: mix.name }));
opts.unshift({ value: 'original', label: 'Original' });
if (jamTrack && mixdowns) {
//mixdowns
const playableMixdowns = mixdowns.filter(mix => mix.packages.find(p => p.file_type === 'mp3' && p.signing_state === 'SIGNED')?.jam_track_mixdown_id === mix.id);
const opts = playableMixdowns.map(mix => ({ value: mix.id, label: `Custom Mix: ${mix.name}`, data: { type: 'custom-mix' } }));
//tracks
const tracks = jamTrack.tracks.filter(track => track.track_type === 'Track' || track.track_type === 'Click').map(track => ({ value: track.id, label: `Stem: ${trackName(track)}`, data: { type: 'stem' } }));
opts.push(...tracks);
//master
opts.unshift({ value: 'master', label: 'Master Mix', data: { type: 'master' } });
setOptions(opts);
if (jamTrack.last_mixdown_id) {
setSelectedOption(opts.find(opt => opt.value === jamTrack.last_mixdown_id));
}else if(jamTrack.last_stem_id){
setSelectedOption(opts.find(opt => opt.value === jamTrack.last_stem_id));
} else {
setSelectedOption(opts[0]);
}
}
}, [jamTrack]);
}, [jamTrack, mixdowns]);
const audioFileExtension = url => {
try {
@ -54,64 +59,39 @@ const JKJamTrackPlayer = () => {
return;
}
if (selectedOption.value === 'original') {
const audioUrl = getOriginalTrackUrl();
if (selectedOption.data.type === 'master') {
const audioUrl = getMasterAudioUrl();
setAudioUrl(audioUrl);
markMixdownActive({ id: jamTrack.id, mixdown_id: null });
if (audioRef.current) {
audioRef.current.load();
}
} else {
//it's a mixdown
console.log('_selectedOption', jamTrack);
let retry = false;
if (jamTrack.mp3Package && jamTrack.mp3Package.signing_state) {
// SIGNED - the package is ready to be downloaded
// 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':
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 long to build. Retry?');
retry = true;
break;
case 'ERROR':
setRetryMessage('Custom mix failed to build. Retry?');
retry = true;
break;
case 'QUIET':
setRetryMessage('Custom mix never got created. Retry?');
retry = true;
break;
default:
break;
}
} else {
setRetryMessage('Custom mix never got created. Retry?');
retry = true;
setAudioUrl(null);
}
if (retry) {
setShowRetry(true);
return;
} else if(selectedOption.data.type === 'custom-mix') {
//it's a mixdown
if (!jamTrack.mp3Package || jamTrack.mp3Package?.signing_state !== 'SIGNED') {
confirmRetryMixdownSigning();
} else if (jamTrack.mp3Package.signing_state === 'SIGNED') {
const activeMix = jamTrack.mixdowns.find(mix => mix.id === selectedOption.value);
getMixdownAudioUrl(activeMix.id).then(audioUrl => {
console.log('_DEBUG_ audioUrl', audioUrl);
setAudioUrl(audioUrl);
markMixdownActive({ id: jamTrack.id, mixdown_id: activeMix.id });
if (audioRef.current) {
audioRef.current.load();
}
});
}
getActiveMixdownUrl().then(audioUrl => {
}else if(selectedOption.data.type === 'stem') {
const activeTrack = jamTrack.tracks.find(track => track.id === selectedOption.value);
console.log('_DEBUG_ activeTrack', activeTrack);
getStemAudioUrl(activeTrack.id).then(audioUrl => {
console.log('_DEBUG_ audioUrl', audioUrl);
setAudioUrl(audioUrl);
markMixdownActive({ id: jamTrack.id, mixdown_id: null, stem_id: activeTrack.id });
if (audioRef.current) {
audioRef.current.load();
}
@ -119,31 +99,27 @@ const JKJamTrackPlayer = () => {
}
}, [selectedOption]);
useEffect(() => {
if (retryDownload && selectedOption) {
const options = { id: selectedOption.value, file_type: 'mp3', encrypt_type: null, sample_rate: SAMPLE_RATE };
const retrySigning = () => {
if (selectedOption) {
const options = {
id: selectedOption.value,
file_type: 'mp3',
encrypt_type: null,
sample_rate: SAMPLE_RATE,
origin: 'player'
};
console.log('enqueueMyMixdown', options);
dispatch(enqueueMyMixdown(options));
enqueueMixdownForSigning(options);
}
}, [retryDownload]);
}
const getOriginalTrackUrl = () => {
const masterTrack = jamTrack.tracks.find(track => track.track_type === 'Master');
if (masterTrack) {
const audioUrl = masterTrack.preview_mp3_url;
return audioUrl;
useEffect(() => {
if (enqueuedMixdown && enqueuedMixdown.origin === 'player') {
console.log('*_DEBUG_ enqueuedMixdown from player', enqueuedMixdown);
showEstimatedTime();
manageWatchedMixdowns();
}
};
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}`;
return audioUrl;
};
}, [enqueuedMixdown]);
return (
<>
@ -160,26 +136,21 @@ const JKJamTrackPlayer = () => {
</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>
<JKSigningRetryConfirmModal
showRetryConfirm={showRetryConfirm}
setShowRetryConfirm={setShowRetryConfirm}
retryConfirmMessage={retryConfirmMessage}
onConfirm={() => {
retrySigning();
setShowRetryConfirm(false);
}}
/>
<JKSigningEstimateTimeModal
showQueueTime={showQueueTime}
setShowQueueTime={setShowQueueTime}
enqueueTimeMessage={enqueueTimeMessage}
/>
</>
);
};

View File

@ -104,6 +104,7 @@ const JKJamTracksFilter = () => {
console.log('jamtracks', data);
setJamTracks(prev => [...prev, ...data.jamtracks]);
setNextOffset(data.next);
setPage(page + 1);
} catch (error) {
console.error('error', error);
}

View File

@ -1,19 +1,37 @@
import React, { useState, useEffect } from 'react';
import { Table } from 'reactstrap';
import React, { useEffect, useState } from 'react';
import { Table, Spinner } from 'reactstrap';
import FingerprintJS from '@fingerprintjs/fingerprintjs';
import { removeMixdown } from '../../store/features/jamTrackSlice';
import { useDispatch, useSelector } from 'react-redux';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { SAMPLE_RATE } from '../../helpers/jamTracks';
import { useJamTrack } from '../../hooks/useJamTrack';
import JKSigningRetryConfirmModal from './JKSigningRetryConfirmModal';
import JKSigningEstimateTimeModal from './JKSigningEstimateTimeModal';
const JKMyJamTrackMixes = () => {
const [selectedMixdown, setSelectedMixdown] = useState(null);
const fpPromise = FingerprintJS.load();
const dispatch = useDispatch();
const jamTrack = useSelector(state => state.jamTrack.jamTrack);
const mixdowns = useSelector(state => state.jamTrack.mixdowns);
const mixdownsLoadingStatus = useSelector(state => state.jamTrack.mixdownsLoadingStatus);
const deleteMixdownStatus = useSelector(state => state.jamTrack.deleteMixdownStatus);
const { jamTrack,
mixdowns,
confirmRetryMixdownSigning,
enqueuedMixdown,
showEstimatedTime,
manageWatchedMixdowns,
enqueueMixdownForSigning,
showRetryConfirm,
setShowRetryConfirm,
retryConfirmMessage,
showQueueTime,
setShowQueueTime,
enqueueTimeMessage,
trackName
} = useJamTrack();
const downloadJamTrack = async () => {
console.log('Downloading JamTrack');
if (!jamTrack.can_download) {
@ -22,22 +40,18 @@ const JKMyJamTrackMixes = () => {
}
const fp = await fpPromise;
const result = await fp.get();
const src = `${process.env.REACT_APP_API_BASE_URL}/jamtracks/${
jamTrack.id
}/stems/master/download.mp3?file_type=mp3&download=1&mark=${result.visitorId}`;
const src = `${process.env.REACT_APP_API_BASE_URL}/jamtracks/${jamTrack.id
}/stems/master/download.mp3?file_type=mp3&download=1&mark=${result.visitorId}`;
openDownload(src);
};
const downloadMix = async mixId => {
console.log('Download mixdown');
const mixdown = mixdowns.find(m => m.id === mixId);
const downloadMix = async (mixdown) => {
const mixdownPackage = mixdown.packages.find(p => p.file_type === 'mp3');
if (mixdownPackage?.signing_state == 'SIGNED') {
const fp = await fpPromise;
const result = await fp.get();
const src = `${process.env.REACT_APP_API_BASE_URL}/mixdowns/${
mixdown.id
}/download.mp3?file_type=mp3&sample_rate=48&download=1&mark=${result.visitorId}`;
const src = `${process.env.REACT_APP_API_BASE_URL}/mixdowns/${mixdown.id
}/download.mp3?file_type=mp3&sample_rate=48&download=1&mark=${result.visitorId}`;
openDownload(src);
} else {
console.log('Mixdown not signed');
@ -51,13 +65,41 @@ const JKMyJamTrackMixes = () => {
document.body.appendChild(iframe);
};
const deleteMix = mixId => {
const deleteMix = mixdown => {
const { id } = mixdown;
if (window.confirm('Delete this custom mix?')) {
console.log('Deleting mixdown', mixId);
dispatch(removeMixdown({ id: mixId }));
console.log('Deleting mixdown', id);
dispatch(removeMixdown({ id }));
}
};
const confirmRetry = (mixdown) => {
setSelectedMixdown(mixdown);
confirmRetryMixdownSigning();
};
const retrySigning = () => {
if (selectedMixdown) {
const options = {
id: selectedMixdown.id,
file_type: 'mp3',
encrypt_type: null,
sample_rate: SAMPLE_RATE,
origin: 'mixes'
};
console.log('enqueueMyMixdown', options);
enqueueMixdownForSigning(options);
}
}
useEffect(() => {
if (enqueuedMixdown && enqueuedMixdown.origin === 'mixes') {
console.log('*_DEBUG_ enqueuedMixdown from mixes', enqueuedMixdown);
showEstimatedTime();
manageWatchedMixdowns();
}
}, [enqueuedMixdown]);
const mixdownPackage = mixdown => {
if (!mixdown.packages || mixdown.packages.length === 0) {
return null;
@ -95,19 +137,16 @@ const JKMyJamTrackMixes = () => {
{mixdowns.map(mixdown => (
<tr key={mixdown.id}>
<td>
{mixdown.name} <br />
{mixdown.id} <br />
pkg-{mixdownPackage(mixdown)?.signing_state}
{mixdown.name}
</td>
<td className="text-center">
{isMixdownPackageReady(mixdown) ? (
<>
<a onClick={() => downloadMix(mixdown.id)} style={{ cursor: 'pointer' }}>
<a onClick={() => downloadMix(mixdown)} style={{ cursor: 'pointer' }}>
<FontAwesomeIcon icon="download" size="lg" className="mr-3" />
</a>
<a
onClick={() => deleteMix(mixdown.id)}
onClick={() => deleteMix(mixdown)}
disabled={deleteMixdownStatus === 'loading'}
style={{ cursor: 'pointer' }}
>
@ -116,18 +155,59 @@ const JKMyJamTrackMixes = () => {
</>
) : (
<>
{mixdown.signing_state === 'QUEUED' || mixdown.signing_state === 'SIGNING' ? (
<FontAwesomeIcon icon="spinner" size="lg" className="mr-3" />
{mixdownPackage(mixdown) && (mixdownPackage(mixdown).signing_state === 'QUEUED' || mixdownPackage(mixdown).signing_state === 'SIGNING') ? (
<Spinner color="primary" size="sm">
Loading...
</Spinner>
) : (
<FontAwesomeIcon icon="exclamation-circle" size="lg" className="mr-3" />
<>
<a onClick={() => confirmRetry(mixdown)} style={{ cursor: 'pointer' }}>
<FontAwesomeIcon icon="exclamation-circle" size="lg" className="mr-3" />
</a>
<a
onClick={() => deleteMix(mixdown)}
disabled={deleteMixdownStatus === 'loading'}
style={{ cursor: 'pointer' }}
>
<FontAwesomeIcon icon="trash" size="lg" />
</a>
</>
)}
</>
)}
</td>
</tr>
))}
{/* {jamTrack && jamTrack.tracks.map(track => (
<tr key={track.id}>
<td>
{trackName(track)}
</td>
<td className="text-center">
<a onClick={() => downloadMix(track)} style={{ cursor: 'pointer' }}>
<FontAwesomeIcon icon="download" size="lg" className="mr-3" />
</a>
</td>
</tr>
))} */}
</tbody>
</Table>
<JKSigningRetryConfirmModal
showRetryConfirm={showRetryConfirm}
setShowRetryConfirm={setShowRetryConfirm}
retryConfirmMessage={retryConfirmMessage}
onConfirm={() => {
retrySigning();
setShowRetryConfirm(false);
}}
/>
<JKSigningEstimateTimeModal
showQueueTime={showQueueTime}
setShowQueueTime={setShowQueueTime}
enqueueTimeMessage={enqueueTimeMessage}
/>
</>
);
};

View File

@ -0,0 +1,19 @@
import React from 'react'
import JKModalDialog from '../common/JKModalDialog'
const JKSigningEstimateTimeModal = ({showQueueTime, setShowQueueTime, enqueueTimeMessage}) => {
return (
<JKModalDialog
show={showQueueTime}
onToggle={() => setShowQueueTime(!showQueueTime)}
title="Mixdown Queued"
showFooter={true}
>
<div className="d-flex flex-column">
<p>{enqueueTimeMessage}</p>
</div>
</JKModalDialog>
)
}
export default JKSigningEstimateTimeModal;

View File

@ -0,0 +1,25 @@
import React from 'react'
import JKModalDialog from '../common/JKModalDialog'
const JKSigningRetryConfirmModal = ({ showRetryConfirm, setShowRetryConfirm, retryConfirmMessage, onConfirm }) => {
return (
<JKModalDialog
show={showRetryConfirm}
onToggle={() => setShowRetryConfirm(!showRetryConfirm)}
title="Retry Download?"
showFooter={false}
>
<div className="d-flex flex-column">
<p>{retryConfirmMessage}</p>
<button
className="btn btn-primary"
onClick={() => onConfirm()}
>
Yes
</button>
</div>
</JKModalDialog>
)
}
export default JKSigningRetryConfirmModal

View File

@ -11,6 +11,11 @@ const ShoppingCartItem = ({ shoppingCartItem, onRemoveItem }) => {
const handleRemoveClick = async () => {
onRemoveItem(id);
}
const productName = (cartItem) => {
//take cartItem.product_info.sale_display, split it by ' - ', and return all except the last element
return cartItem.product_info.sale_display.split(' - ').slice(0, -1).join(' - ');
}
return (
<Row noGutters className="align-items-center px-1 border-bottom border-200">
@ -19,7 +24,7 @@ const ShoppingCartItem = ({ shoppingCartItem, onRemoveItem }) => {
<Media body>
<h5 className="fs-0">
<Link className="text-900" to={`/jamtracks`}>
{ shoppingCartItem.product_info.sale_display }
{ productName(shoppingCartItem) }
</Link>
</h5>
<div

View File

@ -0,0 +1,255 @@
import React, { useState, useEffect, useCallback } from 'react';
import { useDispatch, useSelector } from 'react-redux';
import {
createMyMixdown,
enqueueMyMixdown,
addWatchedMixdown,
removeWatchedMixdown,
fetchJamTrack,
} from '../store/features/jamTrackSlice';
import { SAMPLE_RATE } from '../helpers/jamTracks';
import { createAlert } from '../helpers/rest';
import { useAuth } from '../context/UserAuth';
import FingerprintJS from '@fingerprintjs/fingerprintjs';
export const useJamTrack = () => {
const jamTrack = useSelector(state => state.jamTrack.jamTrack);
const jamTrackLoadingStatus = useSelector(state => state.jamTrack.jamTrackLoadingStatus);
const mixdowns = useSelector(state => state.jamTrack.mixdowns);
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 { currentUser } = useAuth();
const fpPromise = FingerprintJS.load();
const dispatch = useDispatch();
const [showQueueTime, setShowQueueTime] = useState(false);
const [enqueueTimeMessage, setEnqueueTimeMessage] = useState('');
const [showRetryConfirm, setShowRetryConfirm] = useState(false);
const [retryConfirmMessage, setRetryConfirmMessage] = useState('');
useEffect(() => {
if (!jamTrack) {
return
}
//reset watched mixdowns on unload
return () => {
watchedMixdowns.forEach(subscription => {
window.JK.SubscriptionUtils.unsubscribe(subscription.type, subscription.id);
});
};
}, [jamTrack]);
const loadJamTrack = (id) => {
dispatch(fetchJamTrack({ id }));
};
const createMixdown = (options) => {
console.log('*_DEBUG_ createNewMixdown', options);
dispatch(createMyMixdown(options));
};
const enqueueMixdownForSigning = (options) => {
console.log('*_DEBUG_ enqueueMixdownForSigning', options);
dispatch(enqueueMyMixdown(options));
};
const showEstimatedTime = () => {
console.log('*_DEBUG_ showEstimatedTime enqueuedMixdown', enqueuedMixdown);
if(!enqueuedMixdown || !enqueuedMixdown.packages || enqueuedMixdown.packages.length === 0) {
return;
}
const time = enqueuedMixdown.packages[0].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 getMasterAudioUrl = () => {
const masterTrack = jamTrack.tracks.find(track => track.track_type === 'Master');
if (masterTrack) {
const audioUrl = masterTrack.preview_mp3_url;
return audioUrl;
}
};
const getMixdownAudioUrl = async (mixdownId) => {
const activeMix = jamTrack.mixdowns.find(mix => mix.id === mixdownId);
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}`;
return audioUrl;
};
const getStemAudioUrl = async (stemId) => {
const activeTrack = jamTrack.tracks.find(track => track.id === stemId);
const fp = await fpPromise;
const result = await fp.get();
const audioUrl =
process.env.REACT_APP_API_BASE_URL +
`/jamtracks/${jamTrack.id}/stems/${activeTrack.id}/download.mp3?file_type=mp3&mark=${result.visitorId}`;
return audioUrl;
};
const confirmRetryMixdownSigning = () => {
// SIGNED - the package is ready to be downloaded
// 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
console.log('confirmRetryMixdownSigning', jamTrack);
if(!jamTrack.mp3Package) {
setRetryConfirmMessage('Custom mix never got created. Retry?');
showRetryConfirm(true);
}else{
switch (jamTrack.mp3Package.signing_state) {
case 'QUIET_TIMEOUT':
setRetryConfirmMessage('Custom mix never got created. Retry?');
setShowRetryConfirm(true);
break;
case 'QUEUED_TIMEOUT':
setRetryConfirmMessage('Custom mix was never built. Retry?');
setShowRetryConfirm(true);
break;
case 'SIGNING_TIMEOUT':
setRetryConfirmMessage('Custom mix took long to build. Retry?');
setShowRetryConfirm(true);
break;
case 'ERROR':
setRetryConfirmMessage('Custom mix failed to build. Retry?');
setShowRetryConfirm(true);
break;
case 'QUIET':
setRetryConfirmMessage('Custom mix never got created. Retry?');
setShowRetryConfirm(true);
break;
default:
break;
}
}
}
const trackName = track => {
if (track.track_type === 'Track' || track.track_type === 'Click') {
if (track.track_type === 'Click') {
return 'Clicktrack';
} else if (track.instrument) {
const instrumentDescription = track.instrument.description;
const instrumentDescriptionParts = instrumentDescription.split(' ');
let part = '';
// if (track.part && track.part !== instrumentDescription) {
if (track.part && !instrumentDescriptionParts.includes(track.part)) {
part = `(${track.part})`;
}
return `${instrumentDescription} ${part}`;
}
}
};
return { loadJamTrack, jamTrack, jamTrackLoadingStatus, mixdowns, newMixdownLoadingStatus, awaitingMixdown, enqueuedMixdown, enqueuedMixdowns, watchedMixdowns, showQueueTime, setShowQueueTime, enqueueTimeMessage, createMixdown, getMasterAudioUrl, getMixdownAudioUrl, confirmRetryMixdownSigning, retryConfirmMessage, showRetryConfirm, setShowRetryConfirm, enqueueMixdownForSigning, manageWatchedMixdowns, showEstimatedTime, getStemAudioUrl, trackName };
}
export default useJamTrack

View File

@ -13,7 +13,6 @@ const initialState = {
mixdownsLoadingStatus: 'idle',
deleteMixdownStatus: 'idle',
newMixdownLoadingStatus: 'idle',
tempMixdownLoadingStatus: 'idle',
error: null
};
@ -29,7 +28,7 @@ export const createMyMixdown = createAsyncThunk('jamTracks/createMixdown', async
export const removeMixdown = createAsyncThunk('jamTracks/removeMixdown', async (options, thunkAPI) => {
const { id } = options;
const response = await deleteMixdown(id);
await deleteMixdown(id);
return { id };
});
@ -39,9 +38,6 @@ export const enqueueMyMixdown = createAsyncThunk('jamTracks/enqueueMixdown', asy
});
export const jamTrackSlice = createSlice({
name: 'jamTrack',
initialState,
@ -51,7 +47,6 @@ export const jamTrackSlice = createSlice({
const jamTrack = state.jamTrack;
if (jamTrack) {
state.jamTrack.mixdowns = [...jamTrack.mixdowns, payload];
state.tempMixdownLoadingStatus = 'succeeded';
}
},
addWatchedMixdown: (state, action) => {
@ -99,19 +94,17 @@ export const jamTrackSlice = createSlice({
})
.addCase(createMyMixdown.pending, (state, action) => {
state.newMixdownLoadingStatus = 'loading';
state.tempMixdownLoadingStatus = 'loading';
})
.addCase(createMyMixdown.fulfilled, (state, action) => {
console.log('_DEBUG_ createMyMixdown.fulfilled', action.payload);
state.mixdowns = [...state.mixdowns, action.payload];
state.awaitingMixdown = action.payload;
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';
})
.addCase(removeMixdown.pending, (state, action) => {
state.mixdownsLoadingStatus = 'loading';
@ -130,7 +123,11 @@ export const jamTrackSlice = createSlice({
})
.addCase(enqueueMyMixdown.fulfilled, (state, action) => {
console.log('enqueueMyMixdown.fulfilled', action.payload);
const enqueue = { queue_time: action.payload.queue_time, signing_state: action.payload.signing_state };
const enqueue = {
id: action.payload.jam_track_mixdown_id,
packages: [{ ...action.payload }],
origin: action.meta.arg.origin
};
state.enqueuedMixdown = enqueue;
state.enqueuedMixdowns = [...state.enqueuedMixdowns, enqueue];
state.mixdowns = state.mixdowns.map(mix => {
@ -155,8 +152,8 @@ const pickOggPackage = (mixdown) => {
};
const assignPackages = (state) => {
state.jamTrack.mp3Package = state.mixdowns.map(pickMp3Package).filter(p => p);
state.jamTrack.oggPackage = state.mixdowns.map(pickOggPackage).filter(p => p);
state.jamTrack.mp3Package = state.mixdowns.map(pickMp3Package).filter(p => p)[0] || null;
state.jamTrack.oggPackage = state.mixdowns.map(pickOggPackage).filter(p => p)[0] || null;
};
export const { addMixdown, addWatchedMixdown, removeWatchedMixdown } = jamTrackSlice.actions;