Closed anishg-cn closed 2 years ago
@ysulyma please check this, its very urgent.
The snippet you posted worked for me, can you share your full code somehow?
Does this happen when the video is playing and you swap out the DOM, or even when it's paused?
@ysulyma yes when the video is playing, and sorry i cannot share the full code.
I'll keep investigating this, but it's probably safer to switch out the script inside the video. The process for doing this isn't very smooth, but would look like:
import {Script, usePlayer} from "liqvid";
const script2 = new Script([
["example", "2:00"]
]);
const player = usePlayer();
const change = useCallback(() => {
// update markers and duration
player.script.markers = script2.markers;
player.script.playback.duration = script2.playback.duration;
}, []);
@ysulyma when the script changes, will the <Player script={script} />
not get updated automatically?
@anishg-cn is your issue that the hiding feature of from()
and during
will not get updated? Or the contents of the video?
@ysulyma it looks like the script is not updating in the player, when the script is changed in the parent component. So what happens is after updating the script if i try to call script.playback.pause() from the parent component, the player is not pausing, as if the script is not updated.
@anishg-cn yeah, so it's best to copy over the markers and playback duration to the existing script rather than replacing it. Playback
emits a durationchange
event when its duration gets changed, so the time display should automatically update.
I think I know what's causing the getBuffers
error. In the meantime, let me know if this does what you want:
import * as ReactDOM from "react-dom";
import {Audio, Player, Script, Utils, usePlayer} from "liqvid";
import {useEffect, useMemo, useState} from "react";
const {during, from} = Utils.authoring;
const {onClick} = Utils.mobile;
/* the leading | character is because during("") doesn't currently work */
const script1 = new Script([
["|animal", "1:00"],
["|animal/cow", "1:00"]
]);
const script2 = new Script([
["|fruit", "1:00"],
["|fruit/like", "1:00"],
]);
function Lesson() {
return (
<Player script={script1}>
<Switcher />
</Player>
);
}
function Switcher() {
const player = usePlayer();
const [mode, setMode] = useState("animal");
// swap scripts
const events = useMemo(() => onClick<HTMLButtonElement>((e) => {
// blur button so keyboard shortcuts keep working
e.currentTarget.blur();
// update script
player.script.markers = script2.markers;
player.script.playback.duration = script2.playback.duration;
// update mode
setMode("fruit");
}), []);
return (
<div id="root" {...during("|")}> {/* wrapper element is needed due to a bug in player.reparseTree */}
<button {...events}>Change script</button>
{mode === "animal" ? <Content1 /> : <Content2 />}
</div>
);
}
function Content1() {
return (
<section>
<Audio start={0}>
<source src="./audio/animals.mp4" type="audio/mp4" />
<source src="./audio/animals.webm" type="audio/webm" />
</Audio>
This video is about animals
<img alt="Cow" src="./cow.jpg" {...from("|animal/cow")} />
</section>
);
}
function Content2() {
const player = usePlayer();
useEffect(() => {
// re-apply during() and from()
// in future you'll be able to just do player.reparseTree(player.canvas) here,
// but currently there's a bug
/* @ts-ignore reparseTree isn't exposed in Liqvid 2.0, will be in 2.1 */
player.reparseTree(player.canvas.querySelector("[data-during]"));
}, []);
return (
<section>
<Audio start={0}>
<source src="./audio/fruit.mp4" type="audio/mp4" />
<source src="./audio/fruit.webm" type="audio/webm" />
</Audio>
This video is about fruit:
I like <span {...from("|fruit/like")}>apples</span>
</section>
);
}
ReactDOM.render(<Lesson />, document.querySelector("main"));
@ysulyma do I need to use the following code when script changes? if yes, can you please explain what it is doing?
useEffect(() => {
// re-apply during() and from()
// in future you'll be able to just do player.reparseTree(player.canvas) here,
// but currently there's a bug
/* @ts-ignore reparseTree isn't exposed in Liqvid 2.0, will be in 2.1 */
player.reparseTree(player.canvas.querySelector("[data-during]"));
}, []);
I am currently using liqvid version ^2.0.10
@ysulyma Also if i change the audio src, it is not reflecting in the player. Can you tell me if there is a way to change the audio src like the script?
<Audio ref={audioRef} start={0} obstructCanPlayThrough>
<source src={audioSrc} />
</Audio>
@anishg-cn you need to use that if you're using during()
and from()
. Internally, those work by attaching data-during
, data-from-first
, and data-from-last
attributes. When the player is first loaded, it scans its contents for elements with these attributes, and sets up a listener to update the tree when the active marker changes. If new elements are added which have these attributes, the player needs to re-parse that part of the tree.
Even if you're not changing the script, you need to use that if you're adding new elements to the DOM which use data-during
/data-from-first
/data-from-last
, e.g. if you're using those inside MathJax or KaTeX.
@anishg-cn as for the audio src, you can either replace the <Audio>
element entirely like I did in the example above (but it sounds like that was giving you errors), or you can change the src
attribute on the <Audio>
element instead of using <source>
(not ideal for iOS).
The HTML5 spec says:
Dynamically modifying a source element and its attribute when the element is already inserted in a video or audio element will have no effect. To change what is playing, just use the src attribute on the media element directly, possibly making use of the canPlayType() method to pick from amongst available resources. Generally, manipulating source elements manually after the document has been parsed is an unncessarily[sic] complicated approach.
MDN says:
Note: If the
src
property is updated (along with any siblings), the parent HTMLMediaElement's load method should be called when done, since<source>
elements are not re-scanned automatically.
But that doesn't work as well with the declarative approach. Maybe you could do this with audioRef.current.domElement.load()
. I think I'd suggest using JS to decide whether to serve an mp4 or webm, and then use src
on <Audio>
directly rather than <source>
elements.
Btw, this sounds really cool! I experimented just a little bit with dynamic scripts/audio, it's exciting to see someone doing this.
I am getting the following error when I refresh the player after a change in the script:
What i am doing is, I have a state variable
initialized
, and i have used useEffect to check when there is a change in the script. In the useEffect i have set the initialized value to false and then to true with a setTimeout so that the player is removed from dom and re-rendered again so that the script change is reflected.Can you please tell if I am doing anything wrong, or is there a way to resolve the issue?