Skip to content

Commit

Permalink
[TMP-Argus]stream switch via dropdown or monitors
Browse files Browse the repository at this point in the history
  • Loading branch information
walesch-yan committed Jan 20, 2025
1 parent 830012c commit 3ed1917
Show file tree
Hide file tree
Showing 3 changed files with 246 additions and 6 deletions.
211 changes: 211 additions & 0 deletions ui/src/components/Argus/StreamSwitch.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,211 @@
import React, { useState, useEffect } from 'react';
import { useSelector } from 'react-redux';
import { JSMpeg } from '../SampleView/jsmpeg.min.js';

import styles from './StreamSwitch.module.css';

export function StreamSwitch(props) {
const { handleSourceSwitch, numberOfShownMonitors = 3 } = props;
const cameras = useSelector(
(state) => state.beamline.hardwareObjects.argus?.attributes?.camera_streams,
);
const [players, setPlayers] = useState({});
const [streammode, setStreammode] = useState('monitors');
const [currentMonitor, setCurrentMonitor] = useState(0);

useEffect(() => {
if (cameras) {
setPlayers((prevPlayers) => {
if (streammode === 'monitors') {
let new_players = {};
// remove all JSMpeg players from streams that have been removed
if (prevPlayers) {
new_players = Object.fromEntries(
Object.entries(prevPlayers).filter(([key]) => {
if (!Object.keys(cameras).includes(key)) {
prevPlayers[key].destroy();
return false;
}
return true;
}),
);
}

// add players for new streams
Object.keys(cameras).forEach((key) => {
if (prevPlayers && !prevPlayers[key]) {
const curr_canv = document.querySelector(`#${key}-canv`);
const url = `ws://localhost:7000/ws/${key}`;
new_players[key] = new JSMpeg.Player(url, {
canvas: curr_canv,
decodeFirstFrame: true,
preserveDrawingBuffer: false,
protocols: [],
});
new_players[key].stop();
curr_canv.src = url;
}
});

return new_players;
}
return {};
});

setCurrentMonitor((prevMonitor) => {
return Math.max(prevMonitor, Object.entries(cameras).length - 3);
});
}
}, [cameras, streammode]);

useEffect(() => {
if (!cameras) {
return;
}

const keys = Object.keys(cameras);

// hide all canvases
keys.forEach((key) => {
const curr_button = document.querySelector(`#${key}-button`);
if (curr_button) {
curr_button.style.display = 'none';
}
});

// show necessary canvases
for (
let pos = currentMonitor;
pos <= Math.min(keys.length - 1, currentMonitor + 2);
pos++
) {
const curr_button = document.querySelector(`#${keys[pos]}-button`);
if (curr_button) {
curr_button.style.display = 'inline';
}
}
}, [currentMonitor, cameras, streammode]);

let result = null;
// in dropdown mode, we only create a dropdown where each option is one stream,
// upon changing the value, we call the handleSourceSwitch function from props
if (streammode === 'dropdown') {
result = (
<select
title="Camera Streams"
id="streams-dropdown"
onChange={(event) => {
handleSourceSwitch(event.target.value);
}}
>
<option value="">-- Streams --</option>
{Object.entries(cameras).map((stream) => {
const key = stream.slice(0, 1);
const url = `ws://localhost:7000/ws/${stream.slice(0, 1)}`;
return (
<option key={key} value={url}>
{' '}
{key}{' '}
</option>
);
})}
</select>
);
} // in monitors mode, one small monitor is created per stream.
// clicking changes the source, while hovering plays/stops the monitor stream
else if (streammode === 'monitors') {
result = (
<div style={{ verticalAlign: 'center' }}>
{cameras && Object.entries(cameras).length > 3 ? (
<button
type="button"
style={{ border: 'none', background: 'none', width: '4%' }}
onClick={() => {
setCurrentMonitor((prevCurrent) => Math.max(0, prevCurrent - 1));
}}
>
<div
className={styles.triangle}
style={{ transform: 'rotate(-90deg)' }}
/>
</button>
) : null}
{cameras
? Object.entries(cameras).map((stream) => {
const key = stream.slice(0, -1)[0];
const url = `ws://localhost:7000/ws/${stream.slice(0, -1)}`;
return (
<button
key={`${key}-button`}
id={`${key}-button`}
onClick={() => handleSourceSwitch(url)}
onMouseEnter={() => {
if (players && players[key]) {
players[key].play();
}
}}
onMouseLeave={() => {
if (players && players[key]) {
players[key].stop();
}
}}
type="button"
style={{
width: '30%',
}}
>
<canvas
id={`${key}-canv`}
aria-label={`Canvas for ${key}`}
style={{
width: '100%',
height: '180px',
overflow: 'hidden',
}}
/>
{key}
</button>
);
})
: null}
{cameras && Object.entries(cameras).length > 3 ? (
<button
type="button"
style={{ border: 'none', background: 'none', width: '4%' }}
onClick={() =>
setCurrentMonitor((prevCurrent) =>
Math.max(
0,
Math.min(
prevCurrent + 1,
Object.entries(cameras).length - numberOfShownMonitors,
),
),
)
}
>
<div
className={styles.triangle}
style={{ transform: 'rotate(90deg)' }}
/>
</button>
) : null}
</div>
);
}

return cameras ? (
<div style={{ textAlign: 'center' }}>
{result}
<button
type="button"
onClick={() => {
setStreammode(streammode === 'monitors' ? 'dropdown' : 'monitors');
}}
style={{ display: 'block', margin: '10px auto' }}
>
Switch Stream Mode
</button>
</div>
) : null;
}
8 changes: 8 additions & 0 deletions ui/src/components/Argus/StreamSwitch.module.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
.triangle {
display: inline-block;
width: 10px;
height: 10px;
border-left: 10px solid transparent;
border-right: 10px solid transparent;
border-bottom: 20px solid black;
}
33 changes: 27 additions & 6 deletions ui/src/components/SampleView/SampleImage.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import DrawGridPlugin from './DrawGridPlugin';
import SampleControls from '../SampleControls/SampleControls';
import GridForm from './GridForm';
import 'fabric';
import { StreamSwitch } from '../Argus/StreamSwitch.jsx';

import { JSMpeg } from './jsmpeg.min.js';

Expand Down Expand Up @@ -799,10 +800,6 @@ export default class SampleImage extends React.Component {

source = `${source}/${this.props.videoHash}`;

if (this.player) {
this.player.stop();
}

if (canvas) {
this.player = new JSMpeg.Player(source, {
canvas,
Expand All @@ -811,9 +808,9 @@ export default class SampleImage extends React.Component {
protocols: [],
});
this.player.play();
}

canvas.src = source;
canvas.src = source;
}
}
}

Expand Down Expand Up @@ -927,6 +924,25 @@ export default class SampleImage extends React.Component {
this.canvas.requestRenderAll();
}

changeSource = (source) => {
const canvas = document.querySelector('#sample-img');
if (source && canvas) {
if (this.player) {
this.player.stop();
this.player = new JSMpeg.Player(source, {
canvas,
decodeFirstFrame: false,
preserveDrawingBuffer: false,
protocols: [],
autoplay: true,
displayGl: false,
});
this.player.play();
}
canvas.src = source;
}
};

render() {
this.configureGrid();
this.updateGridResults();
Expand Down Expand Up @@ -959,6 +975,11 @@ export default class SampleImage extends React.Component {
<canvas id="canvas" className="coveringCanvas" />
</div>
</div>

<StreamSwitch
handleSourceSwitch={this.changeSource}
streammode="monitors"
/>
</div>
);
}
Expand Down

0 comments on commit 3ed1917

Please sign in to comment.