show audio tracks and session mix

This commit is contained in:
Nuwan 2025-12-08 15:15:47 +05:30
parent f417e16319
commit b2f378e01e
105 changed files with 856 additions and 57 deletions

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 999 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 23 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 896 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 952 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 764 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 961 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 885 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 961 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 911 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 892 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 876 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 900 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 913 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 838 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 758 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 915 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 751 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 944 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 758 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 925 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 997 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1012 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 787 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 961 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 762 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 779 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 864 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

View File

@ -0,0 +1,38 @@
import React from 'react';
import JKSessionMyTrack from './JKSessionMyTrack.js';
const JKSessionAudioInputs = ({ myTracks, chat, mixerHelper, isRemote = false }) => {
return (
<div className='d-flex' style={{ gap: '0.5rem' }}>
<div>
{myTracks.length === 0 && !chat ? (
<div>No tracks available</div>
) : (
<>
{myTracks.map((track, index) => (
<JKSessionMyTrack
key={track.track.client_track_id || index}
{...track}
mode={mixerHelper.mixMode}
isRemote={isRemote}
/>
))}
{chat && (
<JKSessionMyTrack
key="chat"
{...chat}
trackName="Chat"
instrumentIcon="/assets/content/icon_instrument_headphones45.png"
hasMixer={true}
isChat={true}
isRemote={isRemote}
/>
)}
</>
)}
</div>
</div>
);
};
export default JKSessionAudioInputs;

View File

@ -0,0 +1,205 @@
/* JKSessionMyTrack.css */
.session-track {
position: relative;
width: 150px;
padding: 10px;
border: 1px solid #ddd;
border-radius: 8px;
background-color: #fff;
margin-bottom: 10px;
font-size: small;
}
.session-track.my-track {
/* Specific styles for my tracks */
}
.session-track.chat-track {
/* Specific styles for chat tracks */
}
.session-track.has-mixer {
/* Styles when mixer is available */
}
.session-track.no-mixer {
opacity: 0.6;
}
.disabled-track-overlay {
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
background-color: rgba(255, 255, 255, 0.8);
display: none; /* Show when needed */
}
.session-track-contents {
position: relative;
z-index: 1;
}
.name {
margin-bottom: 8px;
text-align: center;
}
.track-avatar {
text-align: center;
margin-bottom: 8px;
}
.track-avatar img {
width: 40px;
height: 40px;
border-radius: 50%;
}
.track-instrument {
text-align: center;
margin-bottom: 8px;
}
.track-instrument img {
width: 45px;
height: 45px;
}
.track-controls {
display: flex;
flex-direction: column;
align-items: center;
gap: 8px;
}
.track-buttons {
display: flex;
align-items: center;
gap: 8px;
}
.track-icon-mute {
width: 24px;
height: 24px;
background-color: #28a745;
border: none;
border-radius: 4px;
cursor: pointer;
display: flex;
align-items: center;
justify-content: center;
color: white;
font-size: 12px;
}
.track-icon-mute.enabled {
background-color: #28a745;
}
.track-icon-mute.muted {
background-color: #dc3545;
}
.track-icon-pan {
width: 24px;
height: 24px;
background-color: #6c757d;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
color: white;
font-size: 10px;
}
.track-icon-equalizer {
width: 24px;
height: 24px;
background-color: #007bff;
border-radius: 4px;
display: flex;
align-items: center;
justify-content: center;
color: white;
font-size: 12px;
}
.track-connection-state {
width: 24px;
height: 24px;
border-radius: 50%;
background-color: #6c757d;
}
.track-connection-state.excellent {
background-color: #28a745;
}
.track-connection-state.good {
background-color: #ffc107;
}
.track-connection-state.poor {
background-color: #fd7e14;
}
.track-connection-state.unknown {
background-color: #6c757d;
}
.track-menu-container {
position: relative;
}
.track-menu-button {
width: 24px;
height: 24px;
background-color: #007bff;
border: none;
border-radius: 4px;
cursor: pointer;
display: flex;
align-items: center;
justify-content: center;
color: white;
font-size: 12px;
}
.track-menu {
position: absolute;
bottom: 100%;
left: 50%;
transform: translateX(-50%);
background-color: white;
border: 1px solid #ddd;
border-radius: 4px;
box-shadow: 0 2px 5px rgba(0, 0, 0, 0.2);
z-index: 10;
min-width: 100px;
}
.track-menu div {
padding: 8px 12px;
cursor: pointer;
border-bottom: 1px solid #eee;
}
.track-menu div:last-child {
border-bottom: none;
}
.track-menu div:hover {
background-color: #f8f9fa;
}
.connection-container {
position: relative;
}
.clearall {
clear: both;
}

View File

@ -0,0 +1,101 @@
import React, { useState, useEffect } from 'react';
import { useMixersContext } from '../../context/MixersContext';
import usePanHelpers from '../../hooks/usePanHelpers';
import SessionTrackVU from './SessionTrackVU';
import SessionTrackGain from './SessionTrackGain';
import TrackDiagnostics from './TrackDiagnostics';
import './JKSessionMyTrack.css';
const JKSessionMyTrack = ({ track, mixers, hasMixer, name, trackName, instrumentIcon, photoUrl, clientId, connStatsClientId, mode, isChat = false, isRemote = false }) => {
const mixerHelper = useMixersContext();
const { convertPanToPercent } = usePanHelpers();
const [pan, setPan] = useState(0);
const [showMenu, setShowMenu] = useState(false);
const [showDiagnostics, setShowDiagnostics] = useState(false);
useEffect(() => {
if (mixers?.mixer) {
setPan(mixers.mixer.pan || 0);
}
}, [mixers]);
const trackClasses = `session-track ${isChat ? 'chat-track' : 'my-track'} ${hasMixer ? 'has-mixer' : 'no-mixer'}`;
const panStyle = {
transform: `rotate(${pan}deg)`,
WebkitTransform: `rotate(${pan}deg)`
};
// Connection state - only show for non-chat tracks
const connectionStateJsx = !isChat ? (
<div
className="track-connection-state unknown"
onMouseEnter={() => setShowDiagnostics(true)}
onMouseLeave={() => setShowDiagnostics(false)}
onClick={() => setShowDiagnostics(!showDiagnostics)}
/>
) : null;
return (
<div className={trackClasses}>
<div className="disabled-track-overlay" />
<div className="session-track-contents">
<div
className="name"
style={{ cursor: 'pointer' }}
onClick={() => window.open(`https://profile.jamkazam.com/user/${clientId}`, '_blank')}
>
{trackName}
</div>
<div
className="track-avatar"
style={{ cursor: 'pointer' }}
onClick={() => window.open(`https://profile.jamkazam.com/user/${clientId}`, '_blank')}
>
<img src={photoUrl} alt="avatar" />
</div>
<div className="track-instrument">
<img height="45" src={instrumentIcon} width="45" alt="instrument" />
</div>
<div className="track-controls">
<div className="vu-and-gain" style={{ display: 'flex', alignItems: 'center', gap: '8px' }}>
<SessionTrackVU
orientation="horizontal"
lightCount={11}
lightWidth={17}
lightHeight={12}
side="best"
mixers={mixers}
/>
<SessionTrackGain
mixers={mixers?.mixer}
gainType="track"
controlGroup="track"
orientation="vertical"
/>
</div>
<div className="track-buttons">
<div className="track-menu-container">
<div className="track-menu-button" onClick={() => setShowMenu(!showMenu)}>...</div>
{showMenu && (
<div className="track-menu">
<div onClick={() => { console.log('Configure'); setShowMenu(false); }}>Configure</div>
<div onClick={() => { console.log('Instrument'); setShowMenu(false); }}>Instrument</div>
{!isRemote && <div onClick={() => { console.log('Plugin'); setShowMenu(false); }}>Plugin</div>}
</div>
)}
</div>
<div className="connection-container">
{connectionStateJsx}
<TrackDiagnostics connStatsClientId={connStatsClientId} isVisible={showDiagnostics} />
</div>
</div>
<br className="clearall" />
</div>
<br className="clearall" />
</div>
</div>
);
};
export default JKSessionMyTrack;

View File

@ -0,0 +1,76 @@
import React, { useMemo } from 'react';
import { useCurrentSessionContext } from '../../context/CurrentSessionContext';
import { useAuth } from '../../context/UserAuth';
import JKSessionAudioInputs from './JKSessionAudioInputs';
import { getAvatarUrl, getInstrumentIcon45 } from '../../helpers/utils';
const JKSessionRemoteTracks = ({ mixerHelper, sessionModel }) => {
const { currentSession } = useCurrentSessionContext();
const { currentUser } = useAuth();
const remoteParticipantsData = useMemo(() => {
if (!currentSession || !currentUser) return [];
// Get all participants (including current user)
const allParticipants = sessionModel.participants();
// Filter out current user to get remote participants
const remoteParticipants = allParticipants.filter(participant =>
participant.user.id !== currentUser.id
);
// Compute tracks for each remote participant
return remoteParticipants.map(participant => {
const tracks = [];
const connStatsClientId = participant.client_role === 'child' && participant.parent_client_id
? participant.parent_client_id
: participant.client_id;
const photoUrl = getAvatarUrl(participant.user.photo_url);
const name = participant.user.name;
for (const track of participant.tracks || []) {
const mixerData = mixerHelper.findMixerForTrack(participant.client_id, track, false, mixerHelper.mixMode);
const hasMixer = !!mixerData.mixer;
const instrumentIcon = getInstrumentIcon45(track.instrument);
const trackName = `${name}: ${track.instrument}`;
tracks.push({
track,
mixerFinder: [participant.client_id, track, false],
mixers: mixerData,
hasMixer,
name,
trackName,
instrumentIcon,
photoUrl,
clientId: participant.client_id,
connStatsClientId
});
}
return {
participant,
tracks,
hasChat: false // Remote participants don't have chat in this context
};
});
}, [currentSession, currentUser, sessionModel, mixerHelper]);
return (
<div className='d-flex' style={{ gap: '1rem' }}>
{remoteParticipantsData.map(({ participant, tracks, hasChat }) => (
<JKSessionAudioInputs
key={participant.client_id}
myTracks={tracks}
chat={hasChat ? {} : null} // No chat for remote users in this implementation
mixerHelper={mixerHelper}
isRemote={true}
/>
))}
</div>
);
};
export default JKSessionRemoteTracks;

Some files were not shown because too many files have changed in this diff Show More