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

feat(CR-152): add API for commit and restore cues #45

Merged
merged 7 commits into from
Dec 15, 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
67 changes: 47 additions & 20 deletions cypress/e2e/timeline.cy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -227,7 +227,7 @@ describe('Timeline plugin', () => {
mockKalturaBe();
loadPlayer().then(player => {
const timelineService = player.getService('timeline');
cy.stub(timelineService, '_isDurationCorrect', () => true);
cy.stub(timelineService.timelineManager, '_isDurationCorrect', () => true);
timelineService.addKalturaCuePoint(10, 'Hotspot', '1');
cy.get('[data-testid="cuePointContainer"]').should('exist');
cy.wait(1000);
Expand All @@ -249,7 +249,7 @@ describe('Timeline plugin', () => {
mockKalturaBe();
loadPlayer().then(player => {
const timelineService = player.getService('timeline');
cy.stub(timelineService, '_isDurationCorrect', () => true);
cy.stub(timelineService.timelineManager, '_isDurationCorrect', () => true);
timelineService.addKalturaCuePoint(0, 'Chapter', '1', 'Chapter 1');
timelineService.addKalturaCuePoint(18, 'Chapter', '2', 'Chapter 2');
cy.get('[data-testid="segmentsWrapper"]').should('exist');
Expand All @@ -261,7 +261,7 @@ describe('Timeline plugin', () => {
mockKalturaBe();
loadPlayer().then(player => {
const timelineService = player.getService('timeline');
cy.stub(timelineService, '_isDurationCorrect', () => true);
cy.stub(timelineService.timelineManager, '_isDurationCorrect', () => true);
timelineService.addKalturaCuePoint(10, 'Chapter', '1', 'Chapter 1');
timelineService.addKalturaCuePoint(18, 'Chapter', '2', 'Chapter 2');
cy.get('[data-testid="segmentsWrapper"]').should('exist');
Expand All @@ -273,7 +273,7 @@ describe('Timeline plugin', () => {
mockKalturaBe();
loadPlayer().then(player => {
const timelineService = player.getService('timeline');
cy.stub(timelineService, '_isDurationCorrect', () => true);
cy.stub(timelineService.timelineManager, '_isDurationCorrect', () => true);
timelineService.addKalturaCuePoint(10, 'Chapter', '1', 'Chapter 1');
timelineService.addKalturaCuePoint(18, 'Chapter', '2', 'Chapter 2');
cy.get('[data-testid="segmentsWrapper"]').should('exist');
Expand All @@ -299,7 +299,7 @@ describe('Timeline plugin', () => {
mockKalturaBe();
loadPlayer().then(player => {
const timelineService = player.getService('timeline');
cy.stub(timelineService, '_isDurationCorrect', () => true);
cy.stub(timelineService.timelineManager, '_isDurationCorrect', () => true);
timelineService.addKalturaCuePoint(0, 'Chapter', '1', 'Chapter 1');
timelineService.addKalturaCuePoint(18, 'Chapter', '2', 'Chapter 2');
cy.get('[data-testid="segmentsWrapper"]').should('exist');
Expand All @@ -321,6 +321,26 @@ describe('Timeline plugin', () => {
});
});
});

it('Should trigger callback method on previes click', done => {
mockKalturaBe();
loadPlayer().then(player => {
const timelineService = player.getService('timeline');
cy.stub(timelineService.timelineManager, '_isDurationCorrect', () => true);
const callback = cy.stub();
timelineService.addKalturaCuePoint(10, 'Hotspot', '1', 'Hotspot 1', {onClick: callback});
cy.get('[data-testid="cuePointContainer"]').should('exist');
cy.wait(1000);
cy.get('[data-testid="cuePointMarkerContainer"]').focus();
cy.get('[data-testid="cuePointPreviewHeaderTitle"]').should('have.text', 'Hotspot');
cy.get('[data-testid="cuePointPreviewHeaderTitle"]')
.click({force: true})
.then(() => {
expect(callback).to.have.been.called;
done();
});
});
});
});

describe('removeCuePoint', () => {
Expand All @@ -340,44 +360,51 @@ describe('Timeline plugin', () => {
});
});
});

it('Should remove all added cue points', done => {
mockKalturaBe();
loadPlayer().then(player => {
const timelineService = player.getService('timeline');
cy.stub(timelineService.timelineManager, '_isDurationCorrect', () => true);
timelineService.addKalturaCuePoint(0, 'Chapter', '1', 'Chapter 1');
timelineService.addKalturaCuePoint(18, 'Chapter', '2', 'Chapter 2');
cy.get('[data-testid="segmentsWrapper"]').children().should('have.length', 2);
timelineService.removeAllKalturaCuePoints();
setTimeout(() => {
cy.get('[data-testid="segmentsWrapper"]').children().should('have.length', 0);
done();
}, 500);
});
});
});

describe('dual-screen timeline preview', () => {
it('Should test single preview on segments', () => {
mockKalturaBe();
loadPlayer().then(player => {
const fakeDualScreenPlugin = {
getDualScreenThumbs: (time: number) => player.getThumbnail(time)
};
player.registerService('dualScreen', fakeDualScreenPlugin);
const timelineService = player.getService('timeline');
cy.stub(timelineService, '_isDurationCorrect', () => true);
timelineService.setGetThumbnailInfo((time: number) => player.getThumbnail(time));
cy.stub(timelineService.timelineManager, '_isDurationCorrect', () => true);
timelineService.addKalturaCuePoint(10, 'Chapter', '1', 'Chapter 1');
cy.get('[data-testid="cuePointPreviewImageContainer"]').children().should('have.length', 1);
});
});
it('Should test dual preview on segments', () => {
mockKalturaBe();
loadPlayer().then(player => {
const fakeDualScreenPlugin = {
getDualScreenThumbs: (time: number) => [player.getThumbnail(time), player.getThumbnail(time)]
};
player.registerService('dualScreen', fakeDualScreenPlugin);
const timelineService = player.getService('timeline');
cy.stub(timelineService, '_isDurationCorrect', () => true);
timelineService.setGetThumbnailInfo((time: number) => [player.getThumbnail(time), player.getThumbnail(time)]);
cy.stub(timelineService.timelineManager, '_isDurationCorrect', () => true);
timelineService.addKalturaCuePoint(10, 'Chapter', '1', 'Chapter 1');
cy.get('[data-testid="cuePointPreviewImageContainer"]').children().should('have.length', 2);
});
});
it('Should test dual preview', () => {
mockKalturaBe();
loadPlayer().then(player => {
const fakeDualScreenPlugin = {
getDualScreenThumbs: (time: number) => [player.getThumbnail(time), player.getThumbnail(time)]
};
player.registerService('dualScreen', fakeDualScreenPlugin);
const timelineService = player.getService('timeline');
timelineService._addSeekBarPreview();
timelineService.setGetThumbnailInfo((time: number) => [player.getThumbnail(time), player.getThumbnail(time)]);
timelineService.addSeekBarPreview();
cy.get('[data-testid="cuePointPreviewImageContainer"]').children().should('have.length', 2);
});
});
Expand Down
8 changes: 6 additions & 2 deletions cypress/e2e/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,11 @@ const getPlayer = () => {
};

const preparePage = (pluginConf = {}, playbackConf = {}) => {
cy.visit('index.html');
cy.visit('index.html', {
onBeforeLoad: (contentWindow: any) => {
contentWindow._TEST_ENV = true;
}
});
return cy.window().then(win => {
try {
// @ts-ignore
Expand All @@ -19,7 +23,7 @@ const preparePage = (pluginConf = {}, playbackConf = {}) => {
},
playback: {muted: true, autoplay: true, ...playbackConf},
plugins: {
timeline: {}
timeline: pluginConf
}
});
return kalturaPlayer.loadMedia({entryId: '0_wifqaipd'});
Expand Down
99 changes: 56 additions & 43 deletions flow-typed/types/cue-point-option.ts
Original file line number Diff line number Diff line change
@@ -1,69 +1,82 @@
import {OnClickEvent} from '@playkit-js/common/dist/hoc/a11y-wrapper';
import {ItemTypes} from '../../src/types/timelineTypes';

export type CuePointOptionsObject = {
marker?: MarkerOptionsObject,
preview?: PreviewOptionsObject
marker?: MarkerOptionsObject;
preview?: PreviewOptionsObject;
};

export type TimedCuePointOptionsObject = CuePointOptionsObject & {
time: number,
presets?: Array<string>
time: number;
presets?: Array<string>;
};

export type KalturaCuePointOptionsObject = {
startTime: number,
type: string,
cuePointId: string,
title?: string,
quizQuestionData?: QuizQuestionData
startTime: number;
type: string;
cuePointId: string;
title?: string;
cuePointData?: QuizQuestionData;
};

export type QuizQuestionData = {
onClick: () => void,
isMarkerDisabled: () => boolean,
index: number,
type: number
onClick: () => void;
isMarkerDisabled: () => boolean;
index: number;
type: number;
};

export type NavigationChapterData = {
onClick?: (
e: OnClickEvent,
byKeyboard: boolean,
cuePoint: {startTime: number; type: ItemTypes.Chapter; cuePointId: string; title?: string}
) => void;
};

export type MarkerOptionsObject = {
get?: Function | string,
props?: Object,
color?: string,
width?: number,
className?: string
get?: Function | string;
props?: Object;
color?: string;
width?: number;
className?: string;
};

export type PreviewOptionsObject = {
get?: Function | string,
props?: Object,
width?: number,
height?: number,
className?: string,
hideTime?: boolean,
sticky?: boolean
get?: Function | string;
props?: Object;
width?: number;
height?: number;
className?: string;
hideTime?: boolean;
sticky?: boolean;
};

export type TimelineMarkerDataObject = MarkerOptionsObject & {
cuePoints: Array<string>,
id?: string,
timelinePreviewRef: any,
timelineMarkerRef: any,
cuePointsData: Array<CuePointMarker>,
useQuizQuestionMarkerSize: boolean,
onMarkerClick: any,
isMarkerDisabled: any
id?: string;
timelinePreviewRef: any;
timelineMarkerRef: any;
cuePointsData: Array<CuePointMarker>;
useQuizQuestionMarkerSize: boolean;
onMarkerClick: any;
isMarkerDisabled: any;
onPreviewClick: (e: OnClickEvent, byKeyboard: boolean) => void;
};

export type CuePointMarker = {
id: string,
type: string,
title: string,
quizQuestionData: any
id: string;
type: string;
title: string;
cuePointData: any;
};

export type Chapter = {
id: string,
title: string,
startTime: number,
endTime: number,
isDummy?: boolean,
isHovered: boolean
id: string;
type: ItemTypes.Chapter;
title: string;
startTime: number;
endTime: number;
isDummy?: boolean;
isHovered: boolean;
onPreviewClick: (e: OnClickEvent, byKeyboard: boolean, chapter: Chapter) => void;
};
7 changes: 5 additions & 2 deletions src/components/chapters/segments-wrapper.scss
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,9 @@
position: absolute;
}

.chapters-container > div:not(:last-child) {
margin-right: 2px;
.chapters-container > div {
border-radius: 4px;
&:not(:last-child) {
margin-right: 4px;
}
}
17 changes: 10 additions & 7 deletions src/components/marker/timeline-preview.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ const translates = {
};

interface TimelinePreviewProps {
toggleNavigationPlugin: (e: OnClickEvent, byKeyboard: boolean, cuePointType: string) => void;
onPreviewClick: (e: OnClickEvent, byKeyboard: boolean, chapter: Chapter) => void;
seekTo: () => void;
cuePointsData: Array<CuePointMarker>;
getThumbnailInfo: () => ThumbnailInfo | ThumbnailInfo[];
Expand Down Expand Up @@ -99,7 +99,11 @@ const Title = ({iconName, children, shouldDisplayTitle = true}: TitleProps) => {
return (
<div className={styles.titleWrapper}>
<Icon size={IconSize.small} name={iconName} />
{shouldDisplayTitle && <span className={styles.title}>{children}</span>}
{shouldDisplayTitle && (
<span className={styles.title} data-testid="cuePointPreviewHeaderTitle">
{children}
</span>
)}
</div>
);
};
Expand Down Expand Up @@ -170,11 +174,11 @@ export class TimelinePreview extends Component<TimelinePreviewProps> {
let quizQuestionTitle = {type: '', firstIndex: 1, lastIndex: ''};
if (quizQuestions.length) {
//@ts-ignore
const reflectionPoint = quizQuestions.find(qq => qq.quizQuestionData.type === 3);
const reflectionPoint = quizQuestions.find(qq => qq.cuePointData.type === 3);
quizQuestionTitle = {
type: quizQuestions.length === 1 && reflectionPoint ? this.props.reflectionPointTranslate! : this.props.questionTranslate!,
firstIndex: quizQuestions[0].quizQuestionData.index + 1,
lastIndex: quizQuestions.length > 1 ? `-${quizQuestions[quizQuestions.length - 1].quizQuestionData.index + 1}` : ''
firstIndex: quizQuestions[0].cuePointData.index + 1,
lastIndex: quizQuestions.length > 1 ? `-${quizQuestions[quizQuestions.length - 1].cuePointData.index + 1}` : ''
};
}

Expand Down Expand Up @@ -279,8 +283,7 @@ export class TimelinePreview extends Component<TimelinePreviewProps> {

onPreviewHeaderClick = (e: OnClickEvent, byKeyboard: boolean) => {
const relevantQuizQuestion = this.props.cuePointsData.find(cp => cp.type === ItemTypes.QuizQuestion);
relevantQuizQuestion ? relevantQuizQuestion.quizQuestionData?.onClick() : this.props.seekTo();
this.props.toggleNavigationPlugin(e, byKeyboard, this.props.cuePointsData[0]?.type || ItemTypes.Chapter);
relevantQuizQuestion ? relevantQuizQuestion.cuePointData?.onClick() : this.props.onPreviewClick(e, byKeyboard, this.props.relevantChapter!);
};

_getPreviewHeaderLeft(): number | null {
Expand Down
Loading
Loading