show audio tracks and session mix
|
After Width: | Height: | Size: 4.0 KiB |
|
After Width: | Height: | Size: 999 B |
|
After Width: | Height: | Size: 23 KiB |
|
After Width: | Height: | Size: 2.2 KiB |
|
After Width: | Height: | Size: 896 B |
|
After Width: | Height: | Size: 11 KiB |
|
After Width: | Height: | Size: 1.8 KiB |
|
After Width: | Height: | Size: 952 B |
|
After Width: | Height: | Size: 14 KiB |
|
After Width: | Height: | Size: 1.9 KiB |
|
After Width: | Height: | Size: 764 B |
|
After Width: | Height: | Size: 5.3 KiB |
|
After Width: | Height: | Size: 1.4 KiB |
|
After Width: | Height: | Size: 961 B |
|
After Width: | Height: | Size: 14 KiB |
|
After Width: | Height: | Size: 1.9 KiB |
|
After Width: | Height: | Size: 2.2 KiB |
|
After Width: | Height: | Size: 885 B |
|
After Width: | Height: | Size: 9.8 KiB |
|
After Width: | Height: | Size: 1.7 KiB |
|
After Width: | Height: | Size: 1.0 KiB |
|
After Width: | Height: | Size: 20 KiB |
|
After Width: | Height: | Size: 2.4 KiB |
|
After Width: | Height: | Size: 1.3 KiB |
|
After Width: | Height: | Size: 10 KiB |
|
After Width: | Height: | Size: 2.7 KiB |
|
After Width: | Height: | Size: 961 B |
|
After Width: | Height: | Size: 14 KiB |
|
After Width: | Height: | Size: 1.9 KiB |
|
After Width: | Height: | Size: 911 B |
|
After Width: | Height: | Size: 8.9 KiB |
|
After Width: | Height: | Size: 1.9 KiB |
|
After Width: | Height: | Size: 892 B |
|
After Width: | Height: | Size: 9.7 KiB |
|
After Width: | Height: | Size: 1.7 KiB |
|
After Width: | Height: | Size: 876 B |
|
After Width: | Height: | Size: 7.3 KiB |
|
After Width: | Height: | Size: 1.7 KiB |
|
After Width: | Height: | Size: 900 B |
|
After Width: | Height: | Size: 10 KiB |
|
After Width: | Height: | Size: 1.7 KiB |
|
After Width: | Height: | Size: 913 B |
|
After Width: | Height: | Size: 8.8 KiB |
|
After Width: | Height: | Size: 1.9 KiB |
|
After Width: | Height: | Size: 838 B |
|
After Width: | Height: | Size: 6.7 KiB |
|
After Width: | Height: | Size: 1.6 KiB |
|
After Width: | Height: | Size: 1.5 KiB |
|
After Width: | Height: | Size: 2.3 KiB |
|
After Width: | Height: | Size: 758 B |
|
After Width: | Height: | Size: 3.8 KiB |
|
After Width: | Height: | Size: 1.3 KiB |
|
After Width: | Height: | Size: 915 B |
|
After Width: | Height: | Size: 10 KiB |
|
After Width: | Height: | Size: 1.8 KiB |
|
After Width: | Height: | Size: 1.3 KiB |
|
After Width: | Height: | Size: 2.9 KiB |
|
After Width: | Height: | Size: 751 B |
|
After Width: | Height: | Size: 5.0 KiB |
|
After Width: | Height: | Size: 1.4 KiB |
|
After Width: | Height: | Size: 944 B |
|
After Width: | Height: | Size: 10 KiB |
|
After Width: | Height: | Size: 1.9 KiB |
|
After Width: | Height: | Size: 758 B |
|
After Width: | Height: | Size: 3.8 KiB |
|
After Width: | Height: | Size: 1.3 KiB |
|
After Width: | Height: | Size: 925 B |
|
After Width: | Height: | Size: 10 KiB |
|
After Width: | Height: | Size: 1.8 KiB |
|
After Width: | Height: | Size: 997 B |
|
After Width: | Height: | Size: 13 KiB |
|
After Width: | Height: | Size: 2.0 KiB |
|
After Width: | Height: | Size: 1012 B |
|
After Width: | Height: | Size: 13 KiB |
|
After Width: | Height: | Size: 2.1 KiB |
|
After Width: | Height: | Size: 1.0 KiB |
|
After Width: | Height: | Size: 15 KiB |
|
After Width: | Height: | Size: 2.1 KiB |
|
After Width: | Height: | Size: 787 B |
|
After Width: | Height: | Size: 5.5 KiB |
|
After Width: | Height: | Size: 1.5 KiB |
|
After Width: | Height: | Size: 961 B |
|
After Width: | Height: | Size: 14 KiB |
|
After Width: | Height: | Size: 1.9 KiB |
|
After Width: | Height: | Size: 762 B |
|
After Width: | Height: | Size: 5.7 KiB |
|
After Width: | Height: | Size: 1.4 KiB |
|
After Width: | Height: | Size: 779 B |
|
After Width: | Height: | Size: 5.6 KiB |
|
After Width: | Height: | Size: 1.4 KiB |
|
After Width: | Height: | Size: 1.5 KiB |
|
After Width: | Height: | Size: 5.6 KiB |
|
After Width: | Height: | Size: 2.1 KiB |
|
After Width: | Height: | Size: 864 B |
|
After Width: | Height: | Size: 5.6 KiB |
|
After Width: | Height: | Size: 1.6 KiB |
|
|
@ -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;
|
||||
|
|
@ -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;
|
||||
}
|
||||
|
|
@ -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;
|
||||
|
|
@ -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;
|
||||