UI improments in JamTrack player

This commit is contained in:
Nuwan 2024-12-29 18:47:55 +05:30
parent 2d7a6db541
commit 8835455795
20 changed files with 187 additions and 84 deletions

View File

@ -263,7 +263,7 @@ describe('Browse sessions', () => {
}
]
});
console.log('_DEBUG_ session', session);
//console.log('_DEBUG_ session', session);
cy.intercept('GET', /\S+\/api\/sessions/, [session]);
cy.visit('/sessions');
cy.get('[data-testid=sessionsListTable] tbody tr').contains('YOU HAVE A FRIEND IN THIS SESSION');

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,14 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg width="100%" height="100%" viewBox="0 0 256 256" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xml:space="preserve" xmlns:serif="http://www.serif.com/" style="fill-rule:evenodd;clip-rule:evenodd;stroke-linejoin:round;stroke-miterlimit:2;">
<g>
<g>
<g>
<path d="M83.9,2.2C80.5,3.6 78.8,4.8 76.1,7.6C71.5,12.5 70.4,16.1 70.4,26.7L70.4,34.1L40.4,34.2L10.3,34.4L10.1,39.3L10,44.3L27,44.4L44,44.6L44.6,233.8L45.8,236.6C49.2,245.1 56.1,251.6 64.5,254.2C67.6,255.2 69.5,255.2 128.2,255.2C186.9,255.2 188.8,255.2 191.9,254.2C200.3,251.6 207.1,245.1 210.6,236.6L211.8,233.8L212.4,44.6L229.4,44.4L246.4,44.3L246,34.5L216,34.3L186,34.2L185.8,25.5C185.6,18.1 185.4,16.4 184.5,14.1C182.4,8.9 177.5,4.1 172.1,2.1C169,1 168.2,1 127.9,1L86.8,1L83.9,2.2ZM170.1,11.8C172.2,12.7 174.3,15.1 175,17.5C175.3,18.5 175.5,22.7 175.5,26.8L175.5,34.2L80.5,34.2L80.5,26.8C80.5,18.6 80.9,16.4 82.9,14.1C85.5,11 83.7,11.2 128,11.1C161.1,11.1 168.8,11.2 170.1,11.8ZM201.6,138.7L201.4,233.2L200.3,235.5C198.7,238.9 195.9,241.7 192.7,243.3L189.8,244.7L66.1,244.7L63.2,243.3C59.9,241.7 57.1,238.8 55.6,235.5L54.5,233.2L54.3,138.7L54.2,44.3L201.7,44.3L201.6,138.7Z" style="fill:rgb(64,124,222);fill-rule:nonzero;"/>
<rect x="79.5" y="71.4" width="10" height="148.4" style="fill:rgb(64,124,222);fill-rule:nonzero;"/>
<path d="M123.2,145.6L123.2,219.8L133,219.4L133.2,145.3L133.3,71.3L123.3,71.3L123.2,145.6Z" style="fill:rgb(64,124,222);fill-rule:nonzero;"/>
<rect x="166.5" y="71.4" width="10" height="148.4" style="fill:rgb(64,124,222);fill-rule:nonzero;"/>
</g>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.8 KiB

View File

@ -0,0 +1,12 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg width="100%" height="100%" viewBox="0 0 256 256" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xml:space="preserve" xmlns:serif="http://www.serif.com/" style="fill-rule:evenodd;clip-rule:evenodd;stroke-linejoin:round;stroke-miterlimit:2;">
<g>
<g>
<g>
<path d="M124.9,25.9L123.1,27.5L122.5,156.7L104.4,139.1C87.8,122.8 86.2,121.5 84.1,121.5C82.4,121.5 81.4,122 80.4,123.2C77.5,126.9 77.3,126.7 101.8,151.2C114.3,163.7 125.3,174.2 126.2,174.5C127.2,174.9 128.4,174.9 129.4,174.5C130.2,174.2 141.3,163.8 153.8,151.4C175.3,130.2 176.7,128.7 176.7,126.6C176.7,123.6 174.5,121.4 171.5,121.4C169.5,121.4 167.7,123 151.4,139.1L133.4,156.9L133.3,92.7C133.2,46.7 132.9,28.1 132.4,27.1C131.7,25.7 129.3,24.2 127.6,24.2C127.2,24.3 125.9,25.1 124.9,25.9Z" style="fill:rgb(64,124,222);fill-rule:nonzero;"/>
<path d="M11.6,147.2L10,148.8L10,227.9L13.8,231.7L127.7,231.7C213,231.7 241.9,231.5 243,230.9C246.1,229.3 246,230.3 246.1,188.6C246.1,149.6 246.1,149 244.7,147.3C242.9,145 239.5,145 237.3,147.2L235.7,148.8L235.7,221.2L20.4,221.2L20.4,185.1C20.4,149.6 20.4,149 19,147.3C17.2,145 13.8,145 11.6,147.2Z" style="fill:rgb(64,124,222);fill-rule:nonzero;"/>
</g>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.4 KiB

View File

@ -1,8 +1,7 @@
.audio-player {
height: 30px;
height: 32px;
background: #ffffff;
//box-shadow: 0 0 20px 0 #000a;
font-family: arial;
color: #1b1a1a;
font-size: 0.75em;
@ -29,7 +28,7 @@
margin-bottom: auto;
margin-left: 5px;
margin-right: 5px;
background: rgb(14, 157, 223);
background: rgb(64, 124, 222);
width: 100%;
height: 4px;
cursor: pointer;
@ -42,8 +41,6 @@
transition: 0.25s;
}
.audio-player .controls .play-pause-button{
background: "none";
border: "none";
@ -94,6 +91,20 @@
width: 120px;
}
.audio-player .controls .download-container {
margin-right: 1px;
cursor: pointer;
position: relative;
z-index: 2;
.download-button {
background: "none";
border: "none";
cursor: "pointer";
outline: "none";
left: 0;
}
}
// .audio-player .controls .toggle-play.play {
// cursor: pointer;
// position: relative;

View File

@ -1,9 +1,11 @@
import React, { useRef, useEffect, useState } from 'react';
import playIcon from '../../assets/img/icons/play-icon.svg';
import pauseIcon from '../../assets/img/icons/pause-icon.svg';
import downloadIcon from '../../assets/img/icons/download-icon.svg';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import PropTypes from 'prop-types';
const JKAudioPlayer = ({ audioUrl }) => {
const JKAudioPlayer = ({ audioUrl, onDownloadClick }) => {
const audioPlayer = useRef(null);
@ -14,7 +16,6 @@ const JKAudioPlayer = ({ audioUrl }) => {
useEffect(() => {
if (!audioUrl) return;
console.log('audioUrl: ' + audioUrl)
//if (audio) { stopAudio(); }
const audio = new Audio(audioUrl);
setAudio(audio);
@ -60,7 +61,7 @@ const JKAudioPlayer = ({ audioUrl }) => {
}
const togglePlayPause = (e) => {
if (!audio || e === "undefined") return;
if (!audio) return;
if (audio.paused) {
audio.play();
setIsPlaying(true);
@ -71,7 +72,7 @@ const JKAudioPlayer = ({ audioUrl }) => {
}
const onClickTimeline = (e) => {
if (!audio || e === "undefined") return;
if (!audio) return;
let timeToSeek = 0;
try {
let timelineWidth = window.getComputedStyle(audioPlayer.current.querySelector(".timeline")).width;
@ -88,7 +89,7 @@ const JKAudioPlayer = ({ audioUrl }) => {
}
const onVolumeSliderClick = (e) => {
if (!audio || e === "undefined") return;
if (!audio) return;
let volume = .75;
try {
let sliderWidth = window.getComputedStyle(audioPlayer.current.querySelector(".controls .volume-slider")).width;
@ -164,6 +165,11 @@ const JKAudioPlayer = ({ audioUrl }) => {
<div className="volume-percentage"></div>
</div>
</div>
<div className='download-container'>
<button className='download-button' onClick={onDownloadClick}>
<img src={downloadIcon} alt='download' width={30} />
</button>
</div>
</div>
</div>
)
@ -171,4 +177,9 @@ const JKAudioPlayer = ({ audioUrl }) => {
}
JKAudioPlayer.propTypes = {
audioUrl: PropTypes.string.isRequired,
onDownloadClick: PropTypes.func.isRequired
}
export default JKAudioPlayer

View File

@ -9,12 +9,14 @@ import JKSigningRetryConfirmModal from './JKSigningRetryConfirmModal';
import JKSigningEstimateTimeModal from './JKSigningEstimateTimeModal';
import { markMixdownActive } from '../../helpers/rest';
import JKAudioPlayer from './JKAudioPlayer';
import FingerprintJS from '@fingerprintjs/fingerprintjs';
const JKJamTrackPlayer = () => {
const [options, setOptions] = useState([]);
const [selectedOption, setSelectedOption] = useState(null);
const [audioUrl, setAudioUrl] = useState(null);
const audioRef = useRef(null);
const fpPromise = FingerprintJS.load();
const { jamTrack, mixdowns, getMasterAudioUrl, getMixdownAudioUrl, confirmRetryMixdownSigning, showQueueTime, setShowQueueTime, enqueueTimeMessage, retryConfirmMessage, showRetryConfirm, setShowRetryConfirm, enqueueMixdownForSigning, enqueuedMixdown, showEstimatedTime, manageWatchedMixdowns, getStemAudioUrl, trackName } = useJamTrack();
@ -76,7 +78,6 @@ const JKJamTrackPlayer = () => {
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) {
@ -87,10 +88,8 @@ const JKJamTrackPlayer = () => {
}
} 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) {
@ -109,26 +108,91 @@ const JKJamTrackPlayer = () => {
sample_rate: SAMPLE_RATE,
origin: 'player'
};
console.log('enqueueMyMixdown', options);
enqueueMixdownForSigning(options);
}
}
useEffect(() => {
if (enqueuedMixdown && enqueuedMixdown.origin === 'player') {
console.log('*_DEBUG_ enqueuedMixdown from player', enqueuedMixdown);
showEstimatedTime();
manageWatchedMixdowns();
}
}, [enqueuedMixdown]);
const handleDownloadClick = async () => {
if (!selectedOption) {
return;
}
if(!jamTrack.can_download) {
console.log('Cannot download JamTrack');
return;
}
if (selectedOption.data.type === 'master') {
await downloadMaster();
} else if (selectedOption.data.type === 'custom-mix') {
const activeMix = jamTrack.mixdowns.find(mix => mix.id === selectedOption.value);
await downloadMixdown(activeMix);
} else if (selectedOption.data.type === 'stem') {
await downloadTrack();
}
}
const downloadMaster = async () => {
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}`;
openDownload(src);
};
const downloadMixdown = 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}`;
openDownload(src);
} else {
console.log('Mixdown not signed');
}
};
const downloadTrack = async (track) => {
const activeTrack = jamTrack.tracks.find(track => track.id === selectedOption.value);
if (activeTrack) {
const fp = await fpPromise;
const result = await fp.get();
const src = `${process.env.REACT_APP_API_BASE_URL}/jamtracks/${jamTrack.id
}/stems/${activeTrack.id}/download.mp3?file_type=mp3&download=1&mark=${result.visitorId}`;
openDownload(src);
}
}
const openDownload = async src => {
const iframe = document.createElement('iframe');
iframe.src = src;
iframe.style.display = 'none';
document.body.appendChild(iframe);
};
return (
<>
<Select options={options} placeholder="Select Mix" onChange={handleOnChange} value={selectedOption} />
<Select
options={options}
placeholder="Select Mix"
onChange={handleOnChange}
value={selectedOption}
styles={{
// Fixes the overlapping problem of the component
menu: (base) => ({ ...base, zIndex: 9999 })
}}
/>
<Row className="mt-2">
<Col>
{audioUrl && (
<JKAudioPlayer audioUrl={audioUrl} />
<JKAudioPlayer audioUrl={audioUrl} onDownloadClick={handleDownloadClick} />
)}
</Col>
</Row>

View File

@ -51,6 +51,14 @@ const JKJamTrackShow = () => {
<div>Loading...</div>
) : jamTrack ? (
<>
<Row>
<Col>
<h4>
JamTrack: {jamTrack.name} by {jamTrack.original_artist}
</h4>
</Col>
</Row>
<Row className='mb-3'>
<Col>
<Link to="/my-jamtracks">{`< Back to My JamTracks`}</Link>

View File

@ -5,6 +5,8 @@ import { Howl } from 'howler';
import { useJamTrackPreview } from '../../context/JamTrackPreviewContext';
import { Spinner } from 'reactstrap';
import PropTypes from 'prop-types';
import playIcon from '../../assets/img/icons/play-icon.svg';
import pauseIcon from '../../assets/img/icons/pause-icon.svg';
const JKJamTrackTrack = ({ track }) => {
const [isPlaying, setIsPlaying] = useState(false);
@ -107,7 +109,14 @@ const JKJamTrackTrack = ({ track }) => {
return (
<div className="mb-1 d-flex align-items-center jamtrack-track">
<span className="mr-1">
<FontAwesomeIcon icon={isPlaying ? 'pause-circle' : 'play-circle'} size="2x" onClick={togglePlay} />
{/* <FontAwesomeIcon icon={isPlaying ? 'pause-circle' : 'play-circle'} size="2x" onClick={togglePlay} /> */}
<button onClick={togglePlay} style={{ border: 'none', background: 'none' }}>
{isPlaying ? (
<img src={pauseIcon} alt="Pause" width="30" />
) : (
<img src={playIcon} alt="Play" width="30" />
)}
</button>
</span>
{trackInfo && (
<>

View File

@ -8,6 +8,7 @@ import { SAMPLE_RATE } from '../../helpers/jamTracks';
import { useJamTrack } from '../../hooks/useJamTrack';
import JKSigningRetryConfirmModal from './JKSigningRetryConfirmModal';
import JKSigningEstimateTimeModal from './JKSigningEstimateTimeModal';
import deleteIcon from '../../assets/img/icons/delete-icon.svg';
const JKMyJamTrackMixes = () => {
const [selectedMixdown, setSelectedMixdown] = useState(null);
@ -32,39 +33,6 @@ const JKMyJamTrackMixes = () => {
trackName
} = useJamTrack();
const downloadJamTrack = async () => {
console.log('Downloading JamTrack');
if (!jamTrack.can_download) {
console.log('Cannot download JamTrack');
return;
}
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}`;
openDownload(src);
};
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}`;
openDownload(src);
} else {
console.log('Mixdown not signed');
}
};
const openDownload = async src => {
const iframe = document.createElement('iframe');
iframe.src = src;
iframe.style.display = 'none';
document.body.appendChild(iframe);
};
const deleteMix = mixdown => {
const { id } = mixdown;
if (window.confirm('Delete this custom mix?')) {
@ -94,7 +62,7 @@ const JKMyJamTrackMixes = () => {
useEffect(() => {
if (enqueuedMixdown && enqueuedMixdown.origin === 'mixes') {
console.log('*_DEBUG_ enqueuedMixdown from mixes', enqueuedMixdown);
//console.log('*_DEBUG_ enqueuedMixdown from mixes', enqueuedMixdown);
showEstimatedTime();
manageWatchedMixdowns();
}
@ -118,22 +86,23 @@ const JKMyJamTrackMixes = () => {
You can save a <strong>maximum of 5 mixes</strong> on JamKazam. If you need to make more mixes, download a mix
to save it, then delete it to make more room
</p>
{ mixdowns.length > 0 && (
<Table striped bordered className="fs--1">
<thead className="bg-200 text-900">
<tr>
<th>Mix</th>
<th className="text-center">Actions</th>
<th className="text-center">Delete</th>
</tr>
</thead>
<tbody>
<tr>
{/* <tr>
<td>Full JamTrack</td>
<td className="text-center">
<a onClick={downloadJamTrack}>
<FontAwesomeIcon icon="download" size="lg" className="mr-3" />
</a>
</td>
</tr>
</tr> */}
{mixdowns.map(mixdown => (
<tr key={mixdown.id}>
<td>
@ -142,15 +111,16 @@ const JKMyJamTrackMixes = () => {
<td className="text-center">
{isMixdownPackageReady(mixdown) ? (
<>
<a onClick={() => downloadMix(mixdown)} style={{ cursor: 'pointer' }}>
{/* <a onClick={() => downloadMix(mixdown)} style={{ cursor: 'pointer' }}>
<FontAwesomeIcon icon="download" size="lg" className="mr-3" />
</a>
</a> */}
<a
onClick={() => deleteMix(mixdown)}
disabled={deleteMixdownStatus === 'loading'}
style={{ cursor: 'pointer' }}
>
<FontAwesomeIcon icon="trash" size="lg" />
{/* <FontAwesomeIcon icon="trash" size="lg" /> */}
<img src={deleteIcon} alt="Delete" height={20} />
</a>
</>
) : (
@ -161,15 +131,18 @@ const JKMyJamTrackMixes = () => {
</Spinner>
) : (
<>
{ mixdownPackage(mixdown) && ['ERROR', 'SIGNING_TIMEOUT', 'QUEUED_TIMEOUT', 'QUIET'].includes(mixdownPackage(mixdown).signing_state) && (
<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" />
{/* <FontAwesomeIcon icon="trash" size="lg" /> */}
<img src={deleteIcon} alt="Delete" height={20} />
</a>
</>
)}
@ -193,6 +166,7 @@ const JKMyJamTrackMixes = () => {
))} */}
</tbody>
</Table>
)}
<JKSigningRetryConfirmModal
showRetryConfirm={showRetryConfirm}
setShowRetryConfirm={setShowRetryConfirm}

View File

@ -11,7 +11,7 @@ export const fetchUserLatencies = createAsyncThunk(
'latency/fetchUserLatencies',
async (options, thunkAPI) => {
const { currentUserId, otherUserIds } = options
console.log('X_DEBUG_ fetchUserLatencies', currentUserId, otherUserIds)
//console.log('_DEBUG_ fetchUserLatencies', currentUserId, otherUserIds)
const response = await getLatencyToUsers(currentUserId, otherUserIds)
return response.json()
}

View File

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

View File

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