Skip to content

Commit

Permalink
Improving narration, adding data-sound, fixing bugs, cleaning up
Browse files Browse the repository at this point in the history
  • Loading branch information
JohnThomson committed Jul 24, 2024
1 parent a4dd205 commit 0ab92ea
Show file tree
Hide file tree
Showing 10 changed files with 259 additions and 1,320 deletions.
1 change: 0 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -110,7 +110,6 @@
"dist/*.css",
"dist/*.tsv"
],
"packageManager": "yarn@1.22.19",
"volta": {
"node": "16.14.0",
"yarn": "1.22.19"
Expand Down
20 changes: 6 additions & 14 deletions src/activities/ActivityContext.ts
Original file line number Diff line number Diff line change
Expand Up @@ -75,11 +75,13 @@ export class ActivityContext {
// NB: if this stops working in storybook; the file should be found because the package.json
// script that starts storybook has a "--static-dir" option that should include the folder
// containing the standard activity sounds.
this.playSound(rightAnswer);
// require on an mp3 gives us some sort of module object where the url of the sound is its 'default'
this.playSound(rightAnswer.default);
}

public playWrong() {
this.playSound(wrongAnswer);
// require on an mp3 gives us some sort of module object where the url of the sound is its 'default'
this.playSound(wrongAnswer.default);
}

private getPagePlayer(): any {
Expand All @@ -96,9 +98,9 @@ export class ActivityContext {
return player;
}

public playSound(url) {
public playSound(url: string) {
const player = this.getPagePlayer();
player.setAttribute("src", url.default);
player.setAttribute("src", url);
player.play();
}

Expand Down Expand Up @@ -138,16 +140,6 @@ export class ActivityContext {
target.addEventListener(name, listener, options);
}

public removeEventListener(
name: string,
target: Element,
listener: EventListener,
options?: AddEventListenerOptions | undefined
) {
// we could try to remove it from this.listeners, but it's harmless to remove it again
target.removeEventListener(name, listener, options);
}

// this is called by the activity manager after it stops the activity.
public stop() {
// detach all the listeners
Expand Down
42 changes: 33 additions & 9 deletions src/activities/activityManager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,10 @@ export interface IActivityObject {
// This is acting on the real DOM, so this is the time to set up event handlers, etc.
showingPage: (context: ActivityContext) => void;

// This is called in place of the normal code that plays sound and animations when the page first appears,
// if the activity's requirements specify soundManagement: true.
doInitialSoundAndAnimation?: (context: ActivityContext) => void;

stop: () => void;
}
// Constructing stuff from interfaces has problems with typescript at the moment.
Expand All @@ -36,7 +40,10 @@ export interface IActivityRequirements {
dragging?: boolean;
clicking?: boolean;
typing?: boolean;
soundManagement?: boolean; // suppress normal sound (and music, and animation)
// suppress normal sound (and music, and animation)
// If this is true, the activity should implement doInitialSoundAndAnimation
// if anything should autoplay when the page appears.
soundManagement?: boolean;
}

// This is the object (implemented by us, not the activity) that represents our own
Expand All @@ -62,19 +69,28 @@ export class ActivityManager {
this.builtInActivities[
simpleCheckboxQuizModule.dataActivityID
] = simpleCheckboxQuizModule as IActivityModule;
this.builtInActivities[
"drag-to-destination"
] = dragToDestinationModule as IActivityModule;
// Review: currently these two use the same module. A lot of stuff is shared, all the way down to the

// Review: currently these all use the same module. A lot of stuff is shared, all the way down to the
// prepareActivity() function in dragActivityRuntime. But a good many specialized TOP types are
// specific to one of the three and not needed for the others. It may be helpful to tease things
// specific to one of them and not needed for the others. It may be helpful to tease things
// apart more, for example, three separate implementations of IActivityModule and PrepareActivity
// which call common code for the setup tasks common to all three.
// which call common code for the setup tasks common to all three. OTOH, in some ways it is simpler
// to have it all in one place, and just do the appropriate initialization based on what kind of
// draggables we find.
this.builtInActivities[
"drag-to-destination" // not currently used
] = dragToDestinationModule as IActivityModule;
this.builtInActivities[
"drag-letter-to-target"
] = dragToDestinationModule as IActivityModule;
this.builtInActivities[
"drag-image-to-target"
] = dragToDestinationModule as IActivityModule;
this.builtInActivities[
"sort-sentence"
"drag-sort-sentence"
] = dragToDestinationModule as IActivityModule;
this.builtInActivities[
"word-chooser-slider"
"word-chooser-slider" // not used yet
] = dragToDestinationModule as IActivityModule;
}
public getActivityAbsorbsDragging(): boolean {
Expand Down Expand Up @@ -221,6 +237,14 @@ export class ActivityManager {
}
}

public doInitialSoundAndAnimation() {
if (this.currentActivity && this.currentActivity.runningObject) {
this.currentActivity.runningObject.doInitialSoundAndAnimation?.(
this.currentActivity.context!
);
}
}

// Showing a new page, so stop any previous activity and start any new one that might be on the new page.
// returns true if this is a page where we are going to have state in the DOM so that the
// container needs to be careful not to get rid of it to save memory.
Expand Down
33 changes: 21 additions & 12 deletions src/activities/dragActivities/DragToDestination.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { ActivityContext } from "../ActivityContext";
import { IActivityObject, IActivityRequirements } from "../activityManager";
import {
playInitialElements,
prepareActivity,
undoPrepareActivity
} from "../../dragActivityRuntime";
Expand All @@ -9,9 +10,10 @@ import {
const activityCss = require("!!raw-loader!./multipleChoiceDomActivity.css")
.default;*/

// This class is intentionally very generic. All it needs is that the html of the
// page it is given should have some objects (typically bloom-textOverPicture) that have
// data-correct-position. These objects are made draggable...[Todo: document more of this as we implement]
// This class is basically an adapter that implements IActivityObject so that activities that
// are created by Bloom's DragActivityTool (the 2024 Bloom Games) can connect to the
// dragActivityRuntime.ts functions that actually do the work of the activity (and are shared with
// Bloom desktop's Play mode).)

// Note that you won't find any code using this directly. Instead,
// it gets used by the ActivityManager as the default export of this module.
Expand All @@ -23,14 +25,17 @@ export default class DragToDestinationActivity implements IActivityObject {
// which is important because the user could be either
// coming back to this page, or going to another instance of this activity
// in a subsequent page.
// eslint-disable-next-line no-unused-vars
public constructor(public pageElement: HTMLElement) {}

public showingPage(activityContext: ActivityContext) {
this.activityContext = activityContext;
this.prepareToDisplayActivityEachTime(activityContext);
}

public doInitialSoundAndAnimation(activityContext: ActivityContext) {
playInitialElements(activityContext.pageElement);
}

// Do just those things that we only want to do once per read of the book.
// In the current implementation of activityManager, this is operating on a copy of the page html,
// NOT the real DOM the user will eventually interact with.
Expand All @@ -39,16 +44,19 @@ export default class DragToDestinationActivity implements IActivityObject {
// The context removes event listeners each time the page is shown, so we have to put them back.
private prepareToDisplayActivityEachTime(activityContext: ActivityContext) {
this.activityContext = activityContext;
// These classes are added one layer outside the page body. This is an element that is a wrapper
// This class is added one layer outside the page body. This is an element that is a wrapper
// for our scoped styles...the furthest out element we can put classes on and have them work
// properly with scoped styles. It's a good place to put classes that affect the state of everything
// in the page.
// in the page. This class indicates in Bloom Desktop that the page is in play mode.
// In Bloom Player, it always is. This helps make a common stylesheet work consistently.
// Bloom Player may also add drag-activity-correct or drag-activity-wrong to this element,
// after checking an answer, or drag-activity-solution when showing the answer.
activityContext.pageElement.parentElement?.classList.add(
"drag-activity-try-it",
"drag-activity-start"
"drag-activity-play"
);
prepareActivity(activityContext.pageElement, next => {
// Move to the next or previous page.
// Move to the next or previous page. None of our current bloom game activities use this, but it's available
// if we create an activity with built-in next/previous page buttons.
if (next) {
activityContext.navigateToNextPage();
} else {
Expand All @@ -63,10 +71,11 @@ export default class DragToDestinationActivity implements IActivityObject {
if (this.activityContext) {
undoPrepareActivity(this.activityContext.pageElement);
this.activityContext.pageElement.parentElement?.classList.remove(
"drag-activity-try-it",
"drag-activity-start",
"drag-activity-play",
"drag-activity-start", // I don't think Bloom Player will ever add this, but just in case.
"drag-activity-correct",
"drag-activity-wrong"
"drag-activity-wrong",
"drag-activity-solution"
);
}
}
Expand Down
43 changes: 18 additions & 25 deletions src/bloom-player-core.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -51,8 +51,8 @@ import {
kLocalStorageBookUrlKey
} from "./bloomPlayerAnalytics";
import { autoPlayType } from "./bloom-player-controls";
import { setCurrentPage } from "./narration";
import {
setCurrentPage as setCurrentNarrationPage,
currentPlaybackMode,
setCurrentPlaybackMode,
PlaybackMode,
Expand All @@ -65,10 +65,10 @@ import {
PlayFailed,
PlayCompleted,
ToggleImageDescription,
pause,
getCurrentPage,
play,
hidingPage,
pauseNarration,
getCurrentNarrationPage,
playNarration,
hidingPage as hidingNarrationPage,
pageHasAudio,
setIncludeImageDescriptions,
playAllSentences
Expand Down Expand Up @@ -473,17 +473,18 @@ export class BloomPlayerCore extends React.Component<IProps, IState> {
? this.sourceUrl
: this.sourceUrl + "/" + filename + ".htm";

let urlPrefixT = haveFullPath
this.urlPrefix = haveFullPath
? this.sourceUrl.substring(
0,
Math.max(slashIndex, encodedSlashIndex)
)
: this.sourceUrl;
if (!urlPrefixT.startsWith("http")) {
if (!this.urlPrefix.startsWith("http")) {
// Only in storybook with local books?
urlPrefixT = window.location.origin + "/" + urlPrefixT;
this.urlPrefix =
window.location.origin + "/" + this.urlPrefix;
}
this.music.urlPrefix = this.urlPrefix = urlPrefixT;
this.music.urlPrefix = this.urlPrefix;
setPlayerUrlPrefix(this.music.urlPrefix);
// Note: this does not currently seem to work when using the storybook fileserver.
// I hypothesize that it automatically filters files starting with a period,
Expand Down Expand Up @@ -544,10 +545,6 @@ export class BloomPlayerCore extends React.Component<IProps, IState> {
}

this.animation.PlayAnimations = this.bookInfo.playAnimations;
console.log(
"animation.PlayAnimations",
this.animation.PlayAnimations
);

this.collectBodyAttributes(body);
this.makeNonEditable(body);
Expand Down Expand Up @@ -1145,13 +1142,9 @@ export class BloomPlayerCore extends React.Component<IProps, IState> {
this.handleToggleImageDescription.bind(this)
);
// allows narration to ask whether swiping to this page is still in progress.
// This doesn't seem to be super reliable, so that narration code also keeps track of
// how long it's been since we switched pages.
setTestIsSwipeInProgress(() => {
console.log(
"animating: " +
this.swiperInstance +
" " +
this.swiperInstance?.animating
);
return this.swiperInstance?.animating;
});
setLogNarration(url => logSound(url, 1));
Expand Down Expand Up @@ -1202,15 +1195,15 @@ export class BloomPlayerCore extends React.Component<IProps, IState> {
// This test determines if we changed pages while paused,
// since the narration object won't yet be updated.
if (
BloomPlayerCore.currentPage !== getCurrentPage() ||
BloomPlayerCore.currentPage !== getCurrentNarrationPage() ||
currentPlaybackMode === PlaybackMode.MediaFinished
) {
this.resetForNewPageAndPlay(BloomPlayerCore.currentPage!);
} else {
if (currentPlaybackMode === PlaybackMode.VideoPaused) {
this.video.play(); // sets currentPlaybackMode = VideoPlaying
} else {
play(); // sets currentPlaybackMode = AudioPlaying
playNarration(); // sets currentPlaybackMode = AudioPlaying
this.animation.PlayAnimation();
this.music.play();
}
Expand Down Expand Up @@ -1437,11 +1430,10 @@ export class BloomPlayerCore extends React.Component<IProps, IState> {
}

private pauseAllMultimedia() {
const temp = currentPlaybackMode;
if (currentPlaybackMode === PlaybackMode.VideoPlaying) {
this.video.pause(); // sets currentPlaybackMode = VideoPaused
} else if (currentPlaybackMode === PlaybackMode.AudioPlaying) {
pause(); // sets currentPlaybackMode = AudioPaused
pauseNarration(); // sets currentPlaybackMode = AudioPaused
this.animation.PauseAnimation();
}
// Music keeps playing after all video, narration, and animation have finished.
Expand Down Expand Up @@ -2400,7 +2392,7 @@ export class BloomPlayerCore extends React.Component<IProps, IState> {
// its continued playing.
this.video.hidingPage();
this.video.HandlePageBeforeVisible(bloomPage);
hidingPage();
hidingNarrationPage();
this.music.hidingPage();
if (
currentPlaybackMode === PlaybackMode.AudioPaused ||
Expand Down Expand Up @@ -2798,7 +2790,7 @@ export class BloomPlayerCore extends React.Component<IProps, IState> {
}
}
this.animation.PlayAnimation(); // get rid of classes that made it pause
setCurrentPage(bloomPage);
setCurrentNarrationPage(bloomPage);
// State must be set before calling HandlePageVisible() and related methods.
if (BloomPlayerCore.currentPageHasVideo) {
setCurrentPlaybackMode(PlaybackMode.VideoPlaying);
Expand All @@ -2813,6 +2805,7 @@ export class BloomPlayerCore extends React.Component<IProps, IState> {

public playAudioAndAnimation(bloomPage: HTMLElement | undefined) {
if (this.activityManager.getActivityManagesSound()) {
this.activityManager.doInitialSoundAndAnimation();
return; // we don't just want to play them all, the activity code will do it selectively.
}
setCurrentPlaybackMode(PlaybackMode.AudioPlaying);
Expand Down
Loading

0 comments on commit 0ab92ea

Please sign in to comment.