Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add new images and improve output playback #10

Merged
merged 1 commit into from
Apr 25, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Binary file added client/public/images/foods/avocado-shadow.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added client/public/images/foods/avocado.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added client/public/images/foods/banana-shadow.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified client/public/images/foods/banana.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added client/public/images/foods/beer-shadow.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added client/public/images/foods/beer.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added client/public/images/foods/lollipop-shadow.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added client/public/images/foods/lollipop.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added client/public/images/foods/maize-shadow.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added client/public/images/foods/maize.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added client/public/images/foods/meat-shadow.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified client/public/images/foods/meat.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added client/public/images/foods/mushroom-shadow.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added client/public/images/foods/mushroom.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added client/public/images/foods/old/banana.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added client/public/images/foods/old/meat.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added client/public/images/foods/old/potato.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added client/public/images/foods/potato-shadow.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified client/public/images/foods/potato.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
36 changes: 20 additions & 16 deletions client/src/components/AudioOutput.jsx
Original file line number Diff line number Diff line change
@@ -1,15 +1,18 @@
import React, { useEffect, useRef } from "react";

function AudioOutput({ currentAudioMessage, isPaused }) {
function AudioOutput({ currentAudioMessage, onFinishedPlaying }) {
const audioRef = useRef(null);
const urlRef = useRef(null);
const checkPlaybackIntervalRef = useRef(null);

useEffect(() => {
// Initialize the audio element if it does not exist
if (!audioRef.current) {
audioRef.current = new Audio();
}
return () => {
// Clean up audio element and interval
clearInterval(checkPlaybackIntervalRef.current);
audioRef.current && audioRef.current.pause();
};
}, []);
Expand All @@ -30,25 +33,26 @@ function AudioOutput({ currentAudioMessage, isPaused }) {
audioRef.current.src = urlRef.current.url;
audioRef.current.load();

// Auto-play the new audio if not paused
if (!isPaused) {
audioRef.current
.play()
.catch((err) => console.error("Error playing audio:", err));
}
}
}, [currentAudioMessage]);

useEffect(() => {
// Manage playback based on isPaused state
if (!isPaused && currentAudioMessage) {
// Auto-play the new audio
audioRef.current
.play()
.catch((err) => console.error("Error playing audio:", err));
} else {
audioRef.current.pause();

// Start checking audio playback status
checkPlaybackIntervalRef.current = setInterval(checkPlaybackStatus, 500);
}
}, [currentAudioMessage]);

const checkPlaybackStatus = () => {
if (
audioRef.current &&
audioRef.current.currentTime >= audioRef.current.duration
) {
// Audio playback has ended
clearInterval(checkPlaybackIntervalRef.current);
onFinishedPlaying();
}
}, [isPaused]);
};

return null; // This component does not render anything itself
}
Expand Down
60 changes: 30 additions & 30 deletions client/src/components/Contact.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,36 +2,36 @@ import React from "react";

function Contact() {
return (
<div>
<p>
The project is an initiative by art & design
<br /> collective Nonhuman Nonsense developed in
<br /> collaboration with Studio Other Spaces,
<br /> In4Art, Elliot, Albin and others.
</p>
<p>
<a
className="link"
href="https://www.instagram.com/nonhuman-nonsense/"
>
@nonhuman-nonsense
</a>
<br />
<a
className="link"
href="https://nonhuman-nonsense.com"
>
nonhuman-nonsense.com
</a>
<br />
<a
className="link"
href="mailto:[email protected]"
>
[email protected]
</a>
</p>
</div>
<div>
<p>
The project is an initiative by art & design
<br /> collective Nonhuman Nonsense developed in
<br /> collaboration with Studio Other Spaces,
<br /> In4Art, Elliot, Albin and others.
</p>
<p>
<a
className="link"
href="https://www.instagram.com/nonhuman-nonsense/"
>
@nonhuman-nonsense
</a>
<br />
<a
className="link"
href="https://nonhuman-nonsense.com"
>
nonhuman-nonsense.com
</a>
<br />
<a
className="link"
href="mailto:[email protected]"
>
[email protected]
</a>
</p>
</div>
);
}

Expand Down
34 changes: 19 additions & 15 deletions client/src/components/Council.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ function Council({ options }) {
const { foods, humanName, topic } = options;
const [activeOverlay, setActiveOverlay] = useState("");
const { width: screenWidth } = useWindowSize();
const [conversation, setConversation] = useState([]); // State to store conversation updates
const [textMessages, setTextMessages] = useState([]); // State to store conversation updates
const [audioMessages, setAudioMessages] = useState([]); // To store multiple ArrayBuffers
const socketRef = useRef(null); // Using useRef to persist socket instance

Expand Down Expand Up @@ -45,8 +45,8 @@ function Council({ options }) {
socketRef.current.emit("start_conversation", promptsAndOptions);

// Listen for conversation text updates
socketRef.current.on("conversation_update", (message) => {
setConversation((prev) => [...prev, message]);
socketRef.current.on("conversation_update", (textMessage) => {
setTextMessages((prev) => [...prev, textMessage]);
});

// Listen for audio updates
Expand Down Expand Up @@ -78,17 +78,17 @@ function Council({ options }) {
return (
<div style={{ height: "100%", width: "100%" }}>
<div className="council wrapper">
{activeOverlay === "" && (
<div
className="text-container"
style={{ justifyContent: "end" }}
>
<Output
conversation={conversation}
audioMessages={audioMessages}
/>
</div>
)}
<div
className="text-container"
style={{ justifyContent: "end" }}
>
{/* Render the Output component regardless of the overlay */}
<Output
textMessages={textMessages}
audioMessages={audioMessages}
isActiveOverlay={activeOverlay !== ""}
/>
</div>
<div style={foodsContainerStyle}>
{foods.map((food, index) => (
<FoodItem
Expand All @@ -101,7 +101,11 @@ function Council({ options }) {
))}
</div>
<Overlay isActive={activeOverlay !== ""}>
<CouncilOverlays activeOverlay={activeOverlay} options={options} removeOverlay={removeOverlay} />
<CouncilOverlays
activeOverlay={activeOverlay}
options={options}
removeOverlay={removeOverlay}
/>
</Overlay>
<Navbar
topic={options.topic}
Expand Down
6 changes: 3 additions & 3 deletions client/src/components/FoodItem.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -84,13 +84,13 @@ function FoodItem({ food, index, total, screenWidth }) {
return (
<div style={foodItemStyle(index, total)}>
<img
src={`/images/foods/${food.name}.png`}
src={`/images/foods/${food.name}-shadow.png`}
style={responsiveStyle}
/>
<img
{/* <img
src={`/images/foods/${food.name}.png`}
style={{ ...responsiveStyle, ...foodImageShadowStyle(index, total) }}
/>
/> */}
</div>
);
}
Expand Down
112 changes: 31 additions & 81 deletions client/src/components/Output.jsx
Original file line number Diff line number Diff line change
@@ -1,102 +1,52 @@
import React, { useState, useEffect } from "react";
import TextOutput from "./TextOutput";
import AudioOutput from "./AudioOutput";
import ConversationControls from "./ConversationControls";

function Output({ conversation, audioMessages }) {
function Output({ textMessages, audioMessages, isActiveOverlay }) {
const [currentMessageIndex, setCurrentMessageIndex] = useState(0);
const [currentSnippetIndex, setCurrentSnippetIndex] = useState(0);
const [currentMessageTextSnippet, setCurrentMessageTextSnippet] =
useState("");
const [currentTextMessage, setCurrentTextMessage] = useState(null);
const [currentAudioMessage, setCurrentAudioMessage] = useState(null);
const [isReady, setIsReady] = useState(false);
const [isPaused, setIsPaused] = useState(true); // Assume initially paused until ready

useEffect(() => {
console.log("Updated audioMessages");
}, [audioMessages]);
tryFindTextAndAudio();
}, [currentMessageIndex, textMessages, audioMessages]);

useEffect(() => {
// Find the audio message only when currentMessageIndex changes
const foundAudioMessage = audioMessages.find(
function tryFindTextAndAudio() {
const textMessage = textMessages[currentMessageIndex];
const audioMessage = audioMessages.find(
(a) => a.message_index === currentMessageIndex
);
if (foundAudioMessage) {
setCurrentAudioMessage(foundAudioMessage);
setIsReady(true);
setIsPaused(false); // Start playing the new message immediately
}
}, [currentMessageIndex, audioMessages]); // Include audioMessages to update the message if it changes

useEffect(() => {
if (conversation.length > 0 && !isPaused) {
const snippets =
conversation[currentMessageIndex].text.split(/(?<=\.\s)/);
if (snippets.length > currentSnippetIndex) {
setCurrentMessageTextSnippet(snippets[currentSnippetIndex]);
const timeout = setTimeout(() => {
if (currentSnippetIndex < snippets.length - 1) {
setCurrentSnippetIndex(currentSnippetIndex + 1);
} else if (currentMessageIndex < conversation.length - 1) {
setCurrentMessageIndex(currentMessageIndex + 1);
setCurrentSnippetIndex(0);
}
}, calculateDisplayTime(snippets[currentSnippetIndex]) * 1000);
return () => clearTimeout(timeout);
}
}
}, [currentMessageIndex, currentSnippetIndex, conversation, isPaused]);
if (
textMessage &&
audioMessage &&
!currentTextMessage &&
!currentAudioMessage
) {
console.log("Both found!");

const calculateDisplayTime = (text) => {
const baseTimePerCharacter = 0.052; // Time per character in seconds
const baseTime = Math.max(3, text.length * baseTimePerCharacter);
const additionalTimeForCommas = (text.match(/,/g) || []).length * 0.5; // 0.4 seconds for each comma
return baseTime + additionalTimeForCommas;
};

function handlePauseResume() {
setIsPaused(!isPaused);
setCurrentTextMessage((prev) => textMessage);
setCurrentAudioMessage((prev) => audioMessage);
}
}

function handleSkipForward() {
if (currentMessageIndex < conversation.length - 1) {
const newIndex = currentMessageIndex + 1;
setCurrentMessageIndex(newIndex);
setCurrentSnippetIndex(0);
// Update the current audio message immediately when skipping
const newAudioMessage = audioMessages.find(
(a) => a.message_index === newIndex
);
setCurrentAudioMessage(newAudioMessage);
setIsPaused(false); // Optionally start playing the new message immediately
}
function handleOnFinishedPlaying() {
setCurrentTextMessage((prev) => null);
setCurrentAudioMessage((prev) => null);
setCurrentMessageIndex((prev) => prev + 1);
}

return (
<div style={{ textAlign: "center", width: "75%" }}>
{isReady ? (
<>
<h2>
Speaker:{" "}
{conversation.length > 0
? conversation[currentMessageIndex].speaker
: ""}
</h2>
<TextOutput currentMessageTextSnippet={currentMessageTextSnippet} />
<AudioOutput
currentAudioMessage={currentAudioMessage}
isPaused={isPaused}
/>
<ConversationControls
isPaused={isPaused}
onPauseResume={handlePauseResume}
onSkipForward={handleSkipForward}
/>
</>
) : (
<h3>The council is getting ready...</h3>
)}
</div>
<>
<TextOutput
currentTextMessage={currentTextMessage}
currentAudioMessage={currentAudioMessage}
/>
<AudioOutput
currentAudioMessage={currentAudioMessage}
onFinishedPlaying={handleOnFinishedPlaying}
/>
</>
);
}

Expand Down
44 changes: 38 additions & 6 deletions client/src/components/TextOutput.jsx
Original file line number Diff line number Diff line change
@@ -1,14 +1,46 @@
import React from "react";
import React, { useState, useEffect } from "react";

function TextOutput({ currentMessageTextSnippet }) {
const textOutputStyle = {
fontFamily: "Arial, sans-serif",
backgroundColor: "rgba(0,0,0,0.7)",
function TextOutput({ currentTextMessage }) {
const [currentSnippetIndex, setCurrentSnippetIndex] = useState(0);
const [currentSnippet, setCurrentSnippet] = useState("");

// Reset the snippet index when a new message is received
useEffect(() => {
setCurrentSnippetIndex(0);
}, [currentTextMessage]);

useEffect(() => {
if (currentTextMessage && currentTextMessage.text) {
const text = currentTextMessage.text;
// Split the text into sentences, ignoring periods followed by a number
const sentences = text.split(/(?<=[.!?])(?=\s+(?![0-9]))/);
setCurrentSnippet(sentences[currentSnippetIndex]);

const interval = setInterval(() => {
setCurrentSnippetIndex((prevIndex) =>
prevIndex < sentences.length - 1 ? prevIndex + 1 : prevIndex
);
}, calculateDisplayTime(currentSnippet) * 1000);
return () => clearInterval(interval);
}
}, [currentTextMessage, currentSnippetIndex]);

// Calculate the display time based on the number of characters in the snippet
const calculateDisplayTime = (text) => {
const baseTimePerCharacter = 0.06; // Adjust this value as needed
return Math.max(3, text.length * baseTimePerCharacter);
};

return (
<div style={{ textAlign: "center" }}>
<h2 style={textOutputStyle}>{currentMessageTextSnippet || ""}</h2>
<h2
style={{
fontFamily: "Arial, sans-serif",
backgroundColor: "rgba(0,0,0,0.7)",
}}
>
{currentSnippet}
</h2>
</div>
);
}
Expand Down