video-plugin.ts•16.4 kB
/**
* Video Plugin - Video Content Element
* Implements video-1 content element for multimedia demonstration
*/
import { BaseContentElementPlugin, RecognitionPattern, EducationalMetadata } from './base-plugin.js';
import { Video1Widget } from '../composer-widget-types.js';
export interface VideoProperties {
title: string;
description?: string;
url?: string;
placeholder: boolean;
settings: {
autoplay: boolean;
controls: boolean;
loop: boolean;
muted: boolean;
startTime?: number; // seconds
endTime?: number; // seconds
playbackSpeed: number;
showCaptions: boolean;
};
dimensions?: {
width: number;
height: number;
aspectRatio: '16:9' | '4:3' | '1:1' | 'custom';
};
interaction?: {
enablePause: boolean;
showProgress: boolean;
allowSeeking: boolean;
showVolume: boolean;
enableFullscreen: boolean;
};
educational?: {
hasTranscript: boolean;
keyMoments: VideoMoment[];
followUpQuestions: string[];
relatedResources: string[];
};
}
export interface VideoMoment {
time: number; // seconds
title: string;
description: string;
type: 'concept' | 'example' | 'transition' | 'summary';
}
export class VideoPlugin extends BaseContentElementPlugin {
type = 'video-1' as const;
name = 'Video Player';
description = 'Interactive video player for multimedia content and demonstrations';
version = '1.0.0';
recognitionPatterns: RecognitionPattern[] = [
{
pattern: /\b(video|watch|view|play)\b/i,
weight: 1.0,
context: ['demonstration', 'comprehension'],
examples: ['Show a video about...', 'Watch the demonstration', 'Video explanation']
},
{
pattern: /\b(demonstrate|show|display|present|exhibit)\b/i,
weight: 0.9,
context: ['demonstration'],
examples: ['Demonstrate the process', 'Show how to...', 'Display the technique']
},
{
pattern: /\b(visual|visually|see|observe|look)\b/i,
weight: 0.7,
context: ['demonstration', 'comprehension'],
examples: ['Visual explanation', 'See the example', 'Observe the process']
},
{
pattern: /\b(tutorial|walkthrough|step-by-step|how\s+to)\b/i,
weight: 0.8,
context: ['demonstration'],
examples: ['Tutorial video', 'Step-by-step guide', 'How to do...']
},
{
pattern: /\b(explanation|example|illustration|demo)\b/i,
weight: 0.6,
context: ['comprehension', 'demonstration'],
examples: ['Video explanation', 'Example demonstration', 'Illustrated concept']
},
{
pattern: /\b(youtube|vimeo|mp4|webm|streaming)\b/i,
weight: 0.5,
context: ['demonstration'],
examples: ['YouTube video', 'MP4 file', 'Streaming content']
}
];
generateWidget(content: string, properties?: Partial<VideoProperties>): Video1Widget {
const videoProps = this.generateVideoProperties(content, properties);
return {
id: this.generateId('video'),
type: this.type,
content_title: null,
padding_top: 35,
padding_bottom: 35,
background_color: '#FFFFFF',
video: videoProps.url || 'https://pocs.digitalpages.com.br/rdpcomposer/media/video-1/video-1.mp4',
dam_assets: []
};
}
getEducationalValue(): EducationalMetadata {
return {
learningTypes: ['demonstration', 'comprehension'],
complexity: 'intermediate',
interactivity: 'medium',
timeEstimate: 15, // 15 minutes average including video + reflection
prerequisites: [],
};
}
private generateVideoProperties(content: string, userProps?: Partial<VideoProperties>): VideoProperties {
const title = this.extractVideoTitle(content);
const description = this.extractDescription(content);
const url = this.extractVideoUrl(content);
const purpose = this.determineVideoPurpose(content);
const settings = this.generateSettings(purpose, userProps?.settings);
const dimensions = this.generateDimensions(purpose, userProps?.dimensions);
const interaction = this.generateInteractionSettings(purpose, userProps?.interaction);
const educational = this.generateEducationalFeatures(content, userProps?.educational);
return {
title,
description,
url,
placeholder: !url,
settings,
dimensions,
interaction,
educational,
...userProps,
};
}
private extractVideoTitle(content: string): string {
// Look for explicit video titles
const titlePatterns = [
/(?:video|watch|view):\s*([^\n.!?]+)/i,
/title:\s*([^\n.!?]+)/i,
/^([^.!?]+(?:video|demonstration|tutorial))/i,
/"([^"]+)"/g, // Quoted titles
];
for (const pattern of titlePatterns) {
const match = content.match(pattern);
if (match && match[1]) {
return match[1].trim().replace(/"/g, '');
}
}
// Generate title from content topic
const firstSentence = content.split(/[.!?]/)[0];
const topic = this.extractMainTopic(firstSentence);
return topic ? `${topic} Video` : 'Educational Video';
}
private extractDescription(content: string): string {
// Look for description patterns
const descPatterns = [
/description:\s*([^\n]+)/i,
/about:\s*([^\n]+)/i,
/this\s+video\s+(?:shows|demonstrates|explains|covers):\s*([^\n.!?]+)/i,
/(?:demonstrates|shows|explains)\s+([^.!?]+)/i,
];
for (const pattern of descPatterns) {
const match = content.match(pattern);
if (match && match[1]) {
return match[1].trim();
}
}
// Generate description from content
const purpose = this.determineVideoPurpose(content);
const topic = this.extractMainTopic(content);
const purposeMap = {
tutorial: `Learn how to ${topic}`,
explanation: `Understand the concepts of ${topic}`,
demonstration: `See ${topic} in action`,
example: `Examples and applications of ${topic}`,
overview: `Overview of ${topic}`,
};
return purposeMap[purpose as keyof typeof purposeMap] || `Educational content about ${topic}`;
}
private extractVideoUrl(content: string): string | undefined {
// Look for video URLs
const urlPatterns = [
/https?:\/\/(?:www\.)?youtube\.com\/watch\?v=[\w-]+/g,
/https?:\/\/youtu\.be\/[\w-]+/g,
/https?:\/\/(?:www\.)?vimeo\.com\/\d+/g,
/https?:\/\/[^\s]+\.(?:mp4|webm|ogg|m4v)/g,
];
for (const pattern of urlPatterns) {
const match = content.match(pattern);
if (match && match[0]) {
return match[0];
}
}
return undefined;
}
private determineVideoPurpose(content: string): string {
const lowerContent = content.toLowerCase();
if (lowerContent.includes('tutorial') || lowerContent.includes('how to') || lowerContent.includes('step')) {
return 'tutorial';
}
if (lowerContent.includes('explain') || lowerContent.includes('understand') || lowerContent.includes('concept')) {
return 'explanation';
}
if (lowerContent.includes('demo') || lowerContent.includes('show') || lowerContent.includes('display')) {
return 'demonstration';
}
if (lowerContent.includes('example') || lowerContent.includes('instance') || lowerContent.includes('case')) {
return 'example';
}
if (lowerContent.includes('overview') || lowerContent.includes('introduction') || lowerContent.includes('summary')) {
return 'overview';
}
return 'explanation'; // Default
}
private generateSettings(purpose: string, userSettings?: Partial<VideoProperties['settings']>): VideoProperties['settings'] {
const baseSettings = {
tutorial: {
autoplay: false,
controls: true,
loop: false,
muted: false,
playbackSpeed: 1.0,
showCaptions: true,
},
explanation: {
autoplay: false,
controls: true,
loop: false,
muted: false,
playbackSpeed: 1.0,
showCaptions: true,
},
demonstration: {
autoplay: false,
controls: true,
loop: true, // Loop demonstrations for repeated viewing
muted: false,
playbackSpeed: 1.0,
showCaptions: false,
},
example: {
autoplay: false,
controls: true,
loop: false,
muted: false,
playbackSpeed: 1.0,
showCaptions: false,
},
overview: {
autoplay: true, // Auto-start overviews
controls: true,
loop: false,
muted: true, // Start muted for auto-play compliance
playbackSpeed: 1.0,
showCaptions: true,
},
};
return {
...baseSettings[purpose as keyof typeof baseSettings] || baseSettings.explanation,
...userSettings,
};
}
private generateDimensions(purpose: string, userDimensions?: Partial<VideoProperties['dimensions']>): VideoProperties['dimensions'] {
const baseDimensions = {
tutorial: { width: 1280, height: 720, aspectRatio: '16:9' as const },
explanation: { width: 1280, height: 720, aspectRatio: '16:9' as const },
demonstration: { width: 1280, height: 720, aspectRatio: '16:9' as const },
example: { width: 1280, height: 720, aspectRatio: '16:9' as const },
overview: { width: 1280, height: 720, aspectRatio: '16:9' as const },
};
return {
...baseDimensions[purpose as keyof typeof baseDimensions] || baseDimensions.explanation,
...userDimensions,
};
}
private generateInteractionSettings(purpose: string, userInteraction?: Partial<VideoProperties['interaction']>): VideoProperties['interaction'] {
const baseInteraction = {
tutorial: {
enablePause: true,
showProgress: true,
allowSeeking: true,
showVolume: true,
enableFullscreen: true,
},
explanation: {
enablePause: true,
showProgress: true,
allowSeeking: true,
showVolume: true,
enableFullscreen: true,
},
demonstration: {
enablePause: true,
showProgress: false, // Hide progress for focused viewing
allowSeeking: false, // Prevent skipping in demonstrations
showVolume: true,
enableFullscreen: true,
},
example: {
enablePause: true,
showProgress: true,
allowSeeking: true,
showVolume: true,
enableFullscreen: true,
},
overview: {
enablePause: true,
showProgress: true,
allowSeeking: true,
showVolume: true,
enableFullscreen: false, // Keep in context for overviews
},
};
return {
...baseInteraction[purpose as keyof typeof baseInteraction] || baseInteraction.explanation,
...userInteraction,
};
}
private generateEducationalFeatures(content: string, userEducational?: Partial<VideoProperties['educational']>): VideoProperties['educational'] {
const keyMoments = this.extractKeyMoments(content);
const followUpQuestions = this.generateFollowUpQuestions(content);
const relatedResources = this.extractRelatedResources(content);
return {
hasTranscript: false, // Default to false, can be enabled
keyMoments,
followUpQuestions,
relatedResources,
...userEducational,
};
}
private extractKeyMoments(content: string): VideoMoment[] {
const moments: VideoMoment[] = [];
// Look for time stamps in content
const timestampPattern = /(\d{1,2}):(\d{2})\s*[-–]\s*([^.\n]+)/g;
let match;
while ((match = timestampPattern.exec(content)) !== null) {
const minutes = parseInt(match[1]);
const seconds = parseInt(match[2]);
const time = minutes * 60 + seconds;
const description = match[3].trim();
moments.push({
time,
title: this.extractSnippet(description, 30),
description,
type: this.classifyMomentType(description),
});
}
// If no explicit timestamps, generate key moments from content structure
if (moments.length === 0) {
const sentences = content.split(/[.!?]+/).filter(s => s.trim().length > 20);
sentences.slice(0, 3).forEach((sentence, index) => {
moments.push({
time: index * 30, // Distribute evenly
title: `Key Point ${index + 1}`,
description: sentence.trim(),
type: 'concept',
});
});
}
return moments;
}
private classifyMomentType(description: string): VideoMoment['type'] {
const lower = description.toLowerCase();
if (lower.includes('example') || lower.includes('instance') || lower.includes('case')) {
return 'example';
}
if (lower.includes('summary') || lower.includes('conclusion') || lower.includes('recap')) {
return 'summary';
}
if (lower.includes('next') || lower.includes('now') || lower.includes('moving')) {
return 'transition';
}
return 'concept'; // Default
}
private generateFollowUpQuestions(content: string): string[] {
const topic = this.extractMainTopic(content);
const purpose = this.determineVideoPurpose(content);
const questionTemplates = {
tutorial: [
`What are the key steps shown in this ${topic} tutorial?`,
`How would you apply these ${topic} techniques?`,
`What challenges might you face when implementing ${topic}?`,
],
explanation: [
`What is the main concept explained about ${topic}?`,
`How does ${topic} relate to what you already know?`,
`What questions do you still have about ${topic}?`,
],
demonstration: [
`What did you observe in the ${topic} demonstration?`,
`How could you modify this ${topic} approach?`,
`What would happen if you changed the ${topic} parameters?`,
],
example: [
`What makes this a good example of ${topic}?`,
`Can you think of similar examples of ${topic}?`,
`How would you explain this ${topic} example to someone else?`,
],
overview: [
`What are the main points covered about ${topic}?`,
`Which aspect of ${topic} interests you most?`,
`What would you like to explore further about ${topic}?`,
],
};
return questionTemplates[purpose as keyof typeof questionTemplates] || questionTemplates.explanation;
}
private extractRelatedResources(content: string): string[] {
const resources: string[] = [];
// Look for explicit resource mentions
const resourcePatterns = [
/(?:see also|reference|link|resource):\s*([^\n.!?]+)/gi,
/(?:more information|additional|further reading):\s*([^\n.!?]+)/gi,
];
resourcePatterns.forEach(pattern => {
let match;
while ((match = pattern.exec(content)) !== null) {
resources.push(match[1].trim());
}
});
// Generate default related resources based on topic
if (resources.length === 0) {
const topic = this.extractMainTopic(content);
resources.push(
`Additional resources about ${topic}`,
`Practice exercises for ${topic}`,
`Advanced concepts in ${topic}`
);
}
return resources.slice(0, 5); // Limit to 5 resources
}
private extractMainTopic(content: string): string {
// Simple topic extraction - same as quiz plugin
const words = content.toLowerCase()
.replace(/[^\w\s]/g, ' ')
.split(/\s+/)
.filter(word => word.length > 3);
const frequency: Record<string, number> = {};
words.forEach(word => {
frequency[word] = (frequency[word] || 0) + 1;
});
const commonWords = ['the', 'and', 'for', 'are', 'but', 'not', 'you', 'all', 'can', 'had', 'her', 'was', 'one', 'our', 'out', 'day', 'get', 'has', 'him', 'his', 'how', 'its', 'may', 'new', 'now', 'old', 'see', 'two', 'who', 'boy', 'did', 'man', 'run', 'say', 'she', 'too', 'use', 'this', 'that', 'with', 'have', 'from', 'they', 'know', 'want', 'been', 'good', 'much', 'some', 'time', 'very', 'when', 'come', 'here', 'just', 'like', 'long', 'make', 'many', 'over', 'such', 'take', 'than', 'them', 'well', 'were'];
const topWords = Object.entries(frequency)
.filter(([word]) => !commonWords.includes(word))
.sort(([,a], [,b]) => b - a);
return topWords.length > 0 ? topWords[0][0] : 'content';
}
}