fix: prevent opening multiple media players simultaneously

- Add media state tracking to JKSessionOpenMenu component
- Disable menu items when any media (JamTrack, Backing Track, Metronome) is open
- Show warning toast when user attempts to open another media type
- Update backing track and metronome track styling and icons
- Adjust close button styling in session screen media sections
- Handle cancelled file selection dialog for backing tracks

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
Nuwan 2026-03-03 16:08:41 +05:30
parent fd8900c15b
commit 388dfe16f8
4 changed files with 87 additions and 26 deletions

View File

@ -10,6 +10,7 @@ import { UncontrolledTooltip } from 'reactstrap';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { faTimes } from '@fortawesome/free-solid-svg-icons';
import './JKSessionMyTrack.css';
import backingTrackIcon from "../../icons/instruments/icon_instrument_default.svg"
const JKSessionBackingTrack = ({
backingTrack,
@ -62,7 +63,21 @@ const JKSessionBackingTrack = ({
<div className={trackClasses}>
<div className="disabled-track-overlay" />
<div className="session-track-contents">
{/* Track display - header removed, now shown in parent */}
{/* Track filename */}
<div className="track-name" style={{
textAlign: 'center',
marginBottom: '4px',
fontSize: '12px',
fontWeight: '500',
color: '#333',
overflow: 'hidden',
textOverflow: 'ellipsis',
whiteSpace: 'nowrap',
maxWidth: '100%',
padding: '0 4px'
}}>
{getFileName(backingTrack)}
</div>
{/* Instrument Icon */}
<div
@ -71,12 +86,12 @@ const JKSessionBackingTrack = ({
display: 'flex',
justifyContent: 'center',
alignItems: 'center',
marginBottom: '8px'
marginBottom: '65px'
}}
>
<img
height="45"
src={backingTrack.instrumentIcon || "/assets/content/icon_recording.png"}
src={backingTrack.instrumentIcon || backingTrackIcon }
width="45"
alt="instrument"
/>
@ -88,8 +103,8 @@ const JKSessionBackingTrack = ({
<SessionTrackVU
orientation="horizontal"
lightCount={11}
lightWidth={17}
lightHeight={12}
lightWidth={25}
lightHeight={15}
side="best"
mixers={mixers}
/>

View File

@ -4,6 +4,7 @@ import { useJamClient } from '../../context/JamClientContext';
import SessionTrackVU from './SessionTrackVU';
import SessionTrackGain from './SessionTrackGain';
import './JKSessionMyTrack.css';
import computerIcon from '../../assets/img/instruments/icon_instrument_computer45_inverted.svg'
const JKSessionMetronome = ({
mixers,
@ -28,7 +29,7 @@ const JKSessionMetronome = ({
<div className={trackClasses}>
<div className="session-track-contents">
{/* Metronome Title */}
<div className="track-title" style={{ textAlign: 'center', marginBottom: '16px', fontSize: '0.9rem', fontWeight: '500', color: '#666' }}>
<div className="track-title" style={{ textAlign: 'center', fontSize: '0.9rem', fontWeight: '500', color: '#666' }}>
Metronome
</div>
@ -39,12 +40,12 @@ const JKSessionMetronome = ({
display: 'flex',
justifyContent: 'center',
alignItems: 'center',
marginBottom: '12px'
marginBottom: '65px'
}}
>
<img
height="55"
src="/assets/content/icon_metronome.png"
src={computerIcon}
width="55"
alt="metronome"
style={{
@ -55,15 +56,15 @@ const JKSessionMetronome = ({
}}
onError={(e) => {
// Fallback if metronome icon doesn't exist
e.target.src = "/assets/content/icon_recording.png";
e.target.src = {computerIcon};
}}
/>
</div>
{/* 0db Label */}
<div style={{ textAlign: 'center', fontSize: '0.75rem', color: '#666', marginBottom: '8px' }}>
{/* <div style={{ textAlign: 'center', fontSize: '0.75rem', color: '#666', marginBottom: '8px' }}>
0db
</div>
</div> */}
{/* Track Controls */}
<div className="track-controls">
@ -72,8 +73,8 @@ const JKSessionMetronome = ({
<SessionTrackVU
orientation="horizontal"
lightCount={11}
lightWidth={17}
lightHeight={12}
lightWidth={25}
lightHeight={15}
side="best"
mixers={mixers}
/>

View File

@ -5,18 +5,43 @@ import { useAuth } from '../../context/UserAuth';
import { toast } from 'react-toastify';
import openIcon from '../../assets/img/client/open.svg';
const JKSessionOpenMenu = ({ onBackingTrackSelected, onJamTrackSelected, onMetronomeSelected }) => {
const JKSessionOpenMenu = ({
onBackingTrackSelected,
onJamTrackSelected,
onMetronomeSelected,
isBackingTrackOpen = false,
isJamTrackOpen = false,
isMetronomeOpen = false
}) => {
const [isOpen, setIsOpen] = useState(false);
const buttonRef = useRef(null);
const menuRef = useRef(null);
const jamClient = useJamClient();
// Check if any media player is currently open
const isAnyMediaOpen = isBackingTrackOpen || isJamTrackOpen || isMetronomeOpen;
// Get the name of currently open media for tooltip/message
const getOpenMediaName = () => {
if (isBackingTrackOpen) return 'Backing Track';
if (isJamTrackOpen) return 'JamTrack';
if (isMetronomeOpen) return 'Metronome';
return null;
};
const toggle = () => setIsOpen(prev => !prev);
const handleMenuItemClick = async (item) => {
console.log(`Selected: ${item}`);
setIsOpen(false);
// Check if any media is already open - prevent opening another
if (isAnyMediaOpen) {
const openMediaName = getOpenMediaName();
toast.warning(`Please close the ${openMediaName} before opening another media.`);
return;
}
if (item === 'JamTracks') {
if (onJamTrackSelected) {
onJamTrackSelected();
@ -27,7 +52,8 @@ const JKSessionOpenMenu = ({ onBackingTrackSelected, onJamTrackSelected, onMetro
window.JK = window.JK || {};
window.JK.HandleBackingTrackSelectedCallback = (result) => {
console.log('Backing track selected:', result);
if (onBackingTrackSelected) {
// Only proceed if a file was actually selected (user didn't cancel)
if (result && result.file && onBackingTrackSelected) {
onBackingTrackSelected(result);
}
};
@ -115,20 +141,26 @@ const JKSessionOpenMenu = ({ onBackingTrackSelected, onJamTrackSelected, onMetro
className="dropdown-menu show"
>
<button
className="dropdown-item"
className={`dropdown-item ${isAnyMediaOpen ? 'disabled' : ''}`}
onClick={() => handleMenuItemClick('JamTracks')}
disabled={isAnyMediaOpen}
style={isAnyMediaOpen ? { opacity: 0.5, cursor: 'not-allowed' } : {}}
>
JamTrack...
</button>
<button
className="dropdown-item"
className={`dropdown-item ${isAnyMediaOpen ? 'disabled' : ''}`}
onClick={() => handleMenuItemClick('Backing Tracks')}
disabled={isAnyMediaOpen}
style={isAnyMediaOpen ? { opacity: 0.5, cursor: 'not-allowed' } : {}}
>
Backing Track...
</button>
<button
className="dropdown-item"
className={`dropdown-item ${isAnyMediaOpen ? 'disabled' : ''}`}
onClick={() => handleMenuItemClick('Metronome')}
disabled={isAnyMediaOpen}
style={isAnyMediaOpen ? { opacity: 0.5, cursor: 'not-allowed' } : {}}
>
Metronome...
</button>

View File

@ -1138,6 +1138,12 @@ const JKSessionScreen = () => {
// console.log('JKSessionScreen: handleBackingTrackSelected called with:', result);
// console.log('JKSessionScreen: Current state - showBackingTrackPopup:', showBackingTrackPopup, 'popupGuard:', popupGuard);
// Don't proceed if no file was selected (user cancelled the dialog)
if (!result || !result.file) {
console.log('JKSessionScreen: No file selected, ignoring');
return;
}
try {
// Use the openBackingTrack action from useMediaActions (already destructured at line 153)
// This handles: jamClient call, Redux state update, and server sync
@ -1351,7 +1357,14 @@ const JKSessionScreen = () => {
<Button className='btn-custom-outline' outline size="md" onClick={handleBroadcast}>
<img src={broadcastIcon} alt="Broadcast" style={{ width: '21px', height: '21px', marginRight: '0.3rem' }} />
Broadcast</Button>
<JKSessionOpenMenu onBackingTrackSelected={handleBackingTrackSelected} onJamTrackSelected={() => dispatch(openModal('jamTrack'))} onMetronomeSelected={handleMetronomeSelected} />
<JKSessionOpenMenu
onBackingTrackSelected={handleBackingTrackSelected}
onJamTrackSelected={() => dispatch(openModal('jamTrack'))}
onMetronomeSelected={handleMetronomeSelected}
isBackingTrackOpen={showBackingTrackPlayer}
isJamTrackOpen={showJamTrackPlayer}
isMetronomeOpen={metronomeState.isOpen}
/>
<JKSessionChatButton sessionId={sessionId} />
<input
type="file"
@ -1428,12 +1441,12 @@ const JKSessionScreen = () => {
JamTrack: {selectedJamTrack.name}
<a
href="#"
className="text-muted ml-2"
className="ml-2"
onClick={(e) => {
e.preventDefault();
handleJamTrackClose();
}}
style={{ fontSize: '1.2em', textDecoration: 'none' }}
style={{ fontSize: '1em', textDecoration: 'none' }}
title="Close JamTrack"
>
<FontAwesomeIcon icon="times" /> Close
@ -1453,15 +1466,15 @@ const JKSessionScreen = () => {
<div style={{ borderLeft: '1px solid #ddd', paddingLeft: '1rem' }}></div>
<div className='backingTrack'>
<h5>
Backing Track: {mixerHelper.backingTracks[0].shortFilename || 'Audio File'}
Backing Track
<a
href="#"
className="text-muted ml-2"
className="ml-2"
onClick={(e) => {
e.preventDefault();
handleBackingTrackMainClose();
}}
style={{ fontSize: '1.2em', textDecoration: 'none' }}
style={{ fontSize: '1em', textDecoration: 'none' }}
title="Close Backing Track"
>
<FontAwesomeIcon icon="times" /> Close
@ -1485,12 +1498,12 @@ const JKSessionScreen = () => {
Metronome
<a
href="#"
className="text-muted ml-2"
className="ml-2"
onClick={(e) => {
e.preventDefault();
handleMetronomeClose();
}}
style={{ fontSize: '1.2em', textDecoration: 'none' }}
style={{ fontSize: '1em', textDecoration: 'none' }}
title="Close Metronome"
>
<FontAwesomeIcon icon="times" /> Close