From 02cc41c61bb50a91f7b18c3a2b976216fb534a80 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakob=20Skov=20S=C3=B8nderg=C3=A5rd?= Date: Thu, 15 Jan 2026 19:15:26 +0100 Subject: [PATCH 1/3] refactor: cleanup and disable autoplay to prevent audio conflicts - Remove VideoTutorial.js (replaced by CustomVideoPlayer) - Disable autoplay on video player to prevent audio context conflicts - Remove autoplay permissions from iframes Co-Authored-By: Claude Opus 4.5 --- src/components/menu/CustomVideoPlayer.js | 43 ++--- src/components/menu/VideoTutorial.js | 203 ----------------------- 2 files changed, 14 insertions(+), 232 deletions(-) delete mode 100644 src/components/menu/VideoTutorial.js diff --git a/src/components/menu/CustomVideoPlayer.js b/src/components/menu/CustomVideoPlayer.js index af92f10..da8abef 100644 --- a/src/components/menu/CustomVideoPlayer.js +++ b/src/components/menu/CustomVideoPlayer.js @@ -4,21 +4,6 @@ import ReactPlayer from "react-player/lazy"; import { Tabs, Tab, Form, Button } from "react-bootstrap"; import Overlay from "../OverlayPlugins/Overlay"; -/** - * CustomVideoPlayer - A customizable video player component - * - * Displays video content in an overlay/modal with: - * - Default URL from settings (US1) - * - Custom URL entry (US2) - * - Reset to default functionality (US3) - * - Draggable overlay display (US4) - * - * @param {string} defaultVideoUrl - Required. The original default video URL (used for reset) - * @param {string} currentVideoUrl - Optional. The current video URL to display - * @param {function} onClose - Required. Callback when overlay closes - * @param {string} initialTab - Optional. Initial tab to display ('Player' or 'Enter_url') - * @param {function} onUrlChange - Optional. Callback when video URL changes - */ // Helper to detect ReverbNation URLs const isReverbNationUrl = (url) => { return url && url.includes("reverbnation.com"); @@ -64,18 +49,20 @@ const getYouTubePlaylistEmbedUrl = (url) => { return url; }; -// Get ReactPlayer config based on URL type -const getPlayerConfig = (url) => { - return { - youtube: { - playerVars: { - modestbranding: 1, - rel: 0, - }, +// ReactPlayer config for YouTube +const PLAYER_CONFIG = { + youtube: { + playerVars: { + modestbranding: 1, + rel: 0, }, - }; + }, }; +/** + * CustomVideoPlayer - A customizable video player component + * Supports YouTube, Vimeo, direct video files, YouTube playlists, and ReverbNation + */ const CustomVideoPlayer = (props) => { const { defaultVideoUrl, currentVideoUrl, onClose, initialTab, onUrlChange } = props; @@ -89,9 +76,8 @@ const CustomVideoPlayer = (props) => { const isReverbNation = isReverbNationUrl(currentUrl); const isPlaylistOnly = isYouTubePlaylistOnly(currentUrl); - // US1: onReady handler + // US1: onReady handler - don't auto-play to avoid audio context conflicts const handlePlayerReady = () => { - setIsPlaying(true); setError(null); }; @@ -167,7 +153,6 @@ const CustomVideoPlayer = (props) => { frameBorder="0" scrolling="no" title="ReverbNation Player" - allow="autoplay" style={{ minHeight: "300px" }} /> ) : isPlaylistOnly ? ( @@ -178,7 +163,7 @@ const CustomVideoPlayer = (props) => { height="100%" frameBorder="0" title="YouTube Playlist" - allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" + allow="accelerometer; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowFullScreen style={{ minHeight: "300px" }} /> @@ -192,7 +177,7 @@ const CustomVideoPlayer = (props) => { controls={true} onReady={handlePlayerReady} onError={handlePlayerError} - config={getPlayerConfig(currentUrl)} + config={PLAYER_CONFIG} /> )} {error && !isReverbNation && !isPlaylistOnly && ( diff --git a/src/components/menu/VideoTutorial.js b/src/components/menu/VideoTutorial.js deleted file mode 100644 index be94b59..0000000 --- a/src/components/menu/VideoTutorial.js +++ /dev/null @@ -1,203 +0,0 @@ -import React, { useRef, useState } from "react"; -import ReactPlayer from "react-player/lazy"; -import { Tabs, Tab, Form, Button } from "react-bootstrap"; - -import Overlay from "./../OverlayPlugins/Overlay"; - -const VideoTutorial = (props) => { - const urlInputRef = useRef(); - const [playing, setPlaying] = useState(false); - const [videoUrl, setVideoUrl] = useState(props.videoUrl); - const [activeTab, setActiveTab] = useState(props.activeVideoTab); - - // TODO:use this : handleChangeActiveVideoTab={this.props.handleChangeActiveVideoTab}, when a tab is selected to persist the selection - const tabKeys = ["Player", "Enter_url", "Tutorials"]; - - const handleSubmit = (event) => { - event.preventDefault(); - setVideoUrl(event.target.elements[0].value); - props.handleChangeVideoUrl(event.target.elements[0].value); - props.handleChangeActiveVideoTab("Player"); - setActiveTab("Player"); - }; - - // //this can be used if we make the tabs controlled - const handleTabSelected = (key) => { - // A bit dummy but need to control tabs after submit (cf handleSumbit()) - - if (tabKeys.includes(key)) { - setActiveTab(key); - props.handleChangeActiveVideoTab(key); - } - // if (key === "Player") { - // setActiveTab("Player"); - // props.handleChangeActiveVideoTab("Player") - // // this.setState({ activeTab: "Player" }); - // } - // if (key === "Enter_url") { - // setActiveTab("Enter_url"); - // props.handleChangeActiveVideoTab("Enter_url") - - // // this.setState({ activeTab: "Enter_url" }); - // } - }; - - const playerOnReady = (event) => { - // A bit dummy but need to control tabs after submit (cf handleSumbit()) - // setPlayerIsReady(false); - setPlaying(true); - }; - - const resetVideoUrl = (event) => { - console.log(props.resetVideoUrl); - setVideoUrl(props.resetVideoUrl); - props.handleResetVideoUrl(); - setActiveTab("Player"); - }; - - return ( - - {/* */} - -
- {/* */} - {/* */} - - - - - - -
-
- - Video URL - - - - - Enter the URL for any YouTube video or playlist that you want to use with - Notio and hit Enter. - - - You are currently watching: - {props.videoUrl} - - {/* Current URL: {videoUrl} */} - - - -
-
-
- -
-
-
Keyboard on/off
- -
- -
-
Notation
- -
- -
-
Ambitus
- -
- -
-
Select different scales
- -
- -
-
Video player
- -
- -
-
Share your setup
- -
-
-
-
-
-
-
- ); -}; - -export default VideoTutorial; From 6b31ebeeeed8f647e75473dfbe66055057d8fa55 Mon Sep 17 00:00:00 2001 From: saxjax Date: Thu, 15 Jan 2026 23:35:32 +0100 Subject: [PATCH 2/3] fix: Safari audio context resume on first user interaction - Import Tone.js in WholeApp - Add click/touch/keydown listeners to resume audio context - Call Tone.start() for Safari compatibility - Cleanup listeners after first interaction and on unmount Co-Authored-By: Claude Opus 4.5 --- src/WholeApp.js | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/src/WholeApp.js b/src/WholeApp.js index 0c86c9a..2aa5967 100644 --- a/src/WholeApp.js +++ b/src/WholeApp.js @@ -10,6 +10,7 @@ import scales from "./data/scalesObj"; import { MobileView } from 'react-device-detect'; import Popup from 'reactjs-popup'; import SoundLibraryNames from "data/TonejsSoundNames"; +import * as Tone from "tone"; // TODO:to meet the requirements for router-dom v6 useParam hook can not be used in class Components and props.match.params only works in v5: //This is using a wrapper function for wholeApp because wholeApp is a class and not a functional component, REWRITE wholeApp to a const wholeApp =()=>{...} @@ -362,6 +363,19 @@ class WholeApp extends Component { // }); // } + // Safari audio fix - resume audio context on first user interaction + resumeAudioContext = async () => { + if (Tone.context.state !== "running") { + await Tone.context.resume(); + await Tone.start(); + console.log("Audio context resumed for Safari"); + } + // Remove listeners after first interaction + document.removeEventListener("click", this.resumeAudioContext); + document.removeEventListener("touchstart", this.resumeAudioContext); + document.removeEventListener("keydown", this.resumeAudioContext); + }; + componentDidMount() { /** * disable right click @@ -370,6 +384,11 @@ class WholeApp extends Component { return false; }; + // Safari/iOS audio fix - add listeners to resume audio context on first user interaction + document.addEventListener("click", this.resumeAudioContext); + document.addEventListener("touchstart", this.resumeAudioContext); + document.addEventListener("keydown", this.resumeAudioContext); + // const { match } = this.props; // const { params } = match; @@ -396,6 +415,13 @@ class WholeApp extends Component { */ } + componentWillUnmount() { + // Cleanup Safari audio fix listeners + document.removeEventListener("click", this.resumeAudioContext); + document.removeEventListener("touchstart", this.resumeAudioContext); + document.removeEventListener("keydown", this.resumeAudioContext); + } + toggleMenu = () => { this.setState({ menuOpen: !this.state.menuOpen }); }; From 7750956a8a8f21d1881b7820336cfa6f8ced1a64 Mon Sep 17 00:00:00 2001 From: saxjax Date: Sat, 17 Jan 2026 23:49:17 +0100 Subject: [PATCH 3/3] feat(mobile): Make mobile warning non-blocking Updates the mobile warning popup to be a non-modal, centered message. This allows users on mobile devices to interact with the application while still seeing a warning about the limited mobile experience, improving usability on smaller devices. Co-Authored-By: Gemini-Pro --- src/WholeApp.js | 30 +++++++++++++++++++----------- 1 file changed, 19 insertions(+), 11 deletions(-) diff --git a/src/WholeApp.js b/src/WholeApp.js index 2aa5967..3b59740 100644 --- a/src/WholeApp.js +++ b/src/WholeApp.js @@ -496,17 +496,25 @@ class WholeApp extends Component { /> -
- } modal open={true} closeOnDocumentClick={false}> -
-

Notio does not have mobile support yet.

-

Please use a computer

+ } + > +
+

Notio has limited mobile support.

+

Please use a computer for the best experience.