mirror of
https://github.com/The-Low-Code-Foundation/OpenNoodl.git
synced 2026-01-12 07:12:54 +01:00
490 lines
14 KiB
Markdown
490 lines
14 KiB
Markdown
# TASK: Video Player Node
|
|
|
|
**Task ID:** NODES-001
|
|
**Priority:** Medium-High
|
|
**Estimated Effort:** 16-24 hours
|
|
**Prerequisites:** React 18.3+ runtime (completed)
|
|
**Status:** Ready for Implementation
|
|
|
|
---
|
|
|
|
## Overview
|
|
|
|
Create a comprehensive Video Player node that handles video playback from URLs or blobs with rich inputs and outputs for complete video management. This addresses a gap in Noodl's visual node offerings - currently users must resort to Function nodes for anything beyond basic video display.
|
|
|
|
### Why This Matters
|
|
|
|
- **Table stakes feature** - Users expect video playback in any modern low-code tool
|
|
- **App builder unlock** - Enables video-centric apps (portfolios, e-learning, social, editors)
|
|
- **Blob support differentiator** - Play local files without server upload (rare in competitors)
|
|
- **Community requested** - Direct request from OpenNoodl community
|
|
|
|
---
|
|
|
|
## Success Criteria
|
|
|
|
- [ ] Video plays from URL (mp4, webm)
|
|
- [ ] Video plays from blob/File object (from File Picker node)
|
|
- [ ] All playback controls work via signal inputs
|
|
- [ ] Time tracking outputs update in real-time
|
|
- [ ] Events fire correctly for all lifecycle moments
|
|
- [ ] Fullscreen and Picture-in-Picture work cross-browser
|
|
- [ ] Frame capture produces valid base64 image
|
|
- [ ] Captions/subtitles display from VTT file
|
|
- [ ] Works in both editor preview and deployed apps
|
|
- [ ] Performance: time updates don't cause UI jank
|
|
|
|
---
|
|
|
|
## Technical Architecture
|
|
|
|
### Node Registration
|
|
|
|
```
|
|
Location: packages/noodl-viewer-react/src/nodes/visual/videoplayer.js (new file)
|
|
Type: Visual/Frontend node using createNodeFromReactComponent
|
|
Category: "Visual" or "UI Elements" > "Media"
|
|
Name: net.noodl.visual.videoplayer
|
|
Display Name: Video Player
|
|
```
|
|
|
|
### Core Implementation Pattern
|
|
|
|
```javascript
|
|
import { createNodeFromReactComponent } from '@noodl/react-component-node';
|
|
|
|
const VideoPlayer = createNodeFromReactComponent({
|
|
name: 'net.noodl.visual.videoplayer',
|
|
displayName: 'Video Player',
|
|
category: 'Visual',
|
|
docs: 'https://docs.noodl.net/nodes/visual/video-player',
|
|
|
|
// Standard visual node frame options
|
|
frame: {
|
|
dimensions: true,
|
|
position: true,
|
|
margins: true,
|
|
align: true
|
|
},
|
|
|
|
allowChildren: false,
|
|
|
|
getReactComponent() {
|
|
return VideoPlayerComponent; // Defined below
|
|
},
|
|
|
|
// ... inputs/outputs defined below
|
|
});
|
|
```
|
|
|
|
### React Component Structure
|
|
|
|
```javascript
|
|
function VideoPlayerComponent(props) {
|
|
const videoRef = useRef(null);
|
|
const [state, setState] = useState({
|
|
isPlaying: false,
|
|
isPaused: true,
|
|
isEnded: false,
|
|
isBuffering: false,
|
|
isSeeking: false,
|
|
isFullscreen: false,
|
|
isPiP: false,
|
|
hasError: false,
|
|
errorMessage: '',
|
|
currentTime: 0,
|
|
duration: 0,
|
|
bufferedPercent: 0,
|
|
videoWidth: 0,
|
|
videoHeight: 0
|
|
});
|
|
|
|
// Use deferred value for time to prevent jank
|
|
const deferredTime = useDeferredValue(state.currentTime);
|
|
|
|
// ... event handlers, effects, signal handlers
|
|
|
|
return (
|
|
<video
|
|
ref={videoRef}
|
|
style={props.style}
|
|
src={props.url || undefined}
|
|
poster={props.posterImage}
|
|
controls={props.controlsVisible}
|
|
loop={props.loop}
|
|
muted={props.muted}
|
|
autoPlay={props.autoplay}
|
|
playsInline={props.playsInline}
|
|
preload={props.preload}
|
|
crossOrigin={props.crossOrigin}
|
|
// ... all event handlers
|
|
>
|
|
{props.captionsUrl && (
|
|
<track
|
|
kind="subtitles"
|
|
src={props.captionsUrl}
|
|
srcLang={props.captionsLanguage || 'en'}
|
|
default={props.captionsEnabled}
|
|
/>
|
|
)}
|
|
</video>
|
|
);
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
## Input/Output Specification
|
|
|
|
### Inputs - Source
|
|
|
|
| Name | Type | Default | Description |
|
|
|------|------|---------|-------------|
|
|
| URL | string | - | Video URL (mp4, webm, ogg, hls) |
|
|
| Blob | any | - | File/Blob object from File Picker |
|
|
| Poster Image | string | - | Thumbnail URL shown before play |
|
|
| Source Type | enum | auto | auto/mp4/webm/ogg/hls |
|
|
|
|
### Inputs - Playback Control (Signals)
|
|
|
|
| Name | Type | Description |
|
|
|------|------|-------------|
|
|
| Play | signal | Start playback |
|
|
| Pause | signal | Pause playback |
|
|
| Toggle Play/Pause | signal | Toggle current state |
|
|
| Stop | signal | Pause and seek to 0 |
|
|
| Seek To | signal | Seek to "Seek Time" value |
|
|
| Skip Forward | signal | Skip forward by "Skip Amount" |
|
|
| Skip Backward | signal | Skip backward by "Skip Amount" |
|
|
|
|
### Inputs - Playback Settings
|
|
|
|
| Name | Type | Default | Description |
|
|
|------|------|---------|-------------|
|
|
| Seek Time | number | 0 | Target time for Seek To (seconds) |
|
|
| Skip Amount | number | 10 | Seconds to skip forward/backward |
|
|
| Volume | number | 1 | Volume level 0-1 |
|
|
| Muted | boolean | false | Mute audio |
|
|
| Playback Rate | number | 1 | Speed: 0.25-4 |
|
|
| Loop | boolean | false | Loop playback |
|
|
| Autoplay | boolean | false | Auto-start on load |
|
|
| Preload | enum | auto | none/metadata/auto |
|
|
| Controls Visible | boolean | true | Show native controls |
|
|
|
|
### Inputs - Advanced
|
|
|
|
| Name | Type | Default | Description |
|
|
|------|------|---------|-------------|
|
|
| Start Time | number | 0 | Auto-seek on load |
|
|
| End Time | number | - | Auto-pause/loop point |
|
|
| Plays Inline | boolean | true | iOS inline playback |
|
|
| Cross Origin | enum | anonymous | anonymous/use-credentials |
|
|
| PiP Enabled | boolean | true | Allow Picture-in-Picture |
|
|
|
|
### Inputs - Captions
|
|
|
|
| Name | Type | Default | Description |
|
|
|------|------|---------|-------------|
|
|
| Captions URL | string | - | VTT subtitle file URL |
|
|
| Captions Enabled | boolean | false | Show captions |
|
|
| Captions Language | string | en | Language code |
|
|
|
|
### Inputs - Actions (Signals)
|
|
|
|
| Name | Type | Description |
|
|
|------|------|-------------|
|
|
| Enter Fullscreen | signal | Request fullscreen mode |
|
|
| Exit Fullscreen | signal | Exit fullscreen mode |
|
|
| Toggle Fullscreen | signal | Toggle fullscreen state |
|
|
| Enter PiP | signal | Enter Picture-in-Picture |
|
|
| Exit PiP | signal | Exit Picture-in-Picture |
|
|
| Capture Frame | signal | Capture current frame to output |
|
|
| Reload | signal | Reload video source |
|
|
|
|
### Outputs - State
|
|
|
|
| Name | Type | Description |
|
|
|------|------|-------------|
|
|
| Is Playing | boolean | Currently playing |
|
|
| Is Paused | boolean | Currently paused |
|
|
| Is Ended | boolean | Playback ended |
|
|
| Is Buffering | boolean | Waiting for data |
|
|
| Is Seeking | boolean | Currently seeking |
|
|
| Is Fullscreen | boolean | In fullscreen mode |
|
|
| Is Picture-in-Picture | boolean | In PiP mode |
|
|
| Has Error | boolean | Error occurred |
|
|
| Error Message | string | Error description |
|
|
|
|
### Outputs - Time
|
|
|
|
| Name | Type | Description |
|
|
|------|------|-------------|
|
|
| Current Time | number | Current position (seconds) |
|
|
| Duration | number | Total duration (seconds) |
|
|
| Progress | number | Position 0-1 |
|
|
| Remaining Time | number | Time remaining (seconds) |
|
|
| Formatted Current | string | "1:23" or "1:23:45" |
|
|
| Formatted Duration | string | Total as formatted string |
|
|
| Formatted Remaining | string | Remaining as formatted string |
|
|
|
|
### Outputs - Media Info
|
|
|
|
| Name | Type | Description |
|
|
|------|------|-------------|
|
|
| Video Width | number | Native video width |
|
|
| Video Height | number | Native video height |
|
|
| Aspect Ratio | number | Width/height ratio |
|
|
| Buffered Percent | number | Download progress 0-1 |
|
|
| Ready State | number | HTML5 readyState 0-4 |
|
|
|
|
### Outputs - Events (Signals)
|
|
|
|
| Name | Type | Description |
|
|
|------|------|-------------|
|
|
| Loaded Metadata | signal | Duration/dimensions available |
|
|
| Can Play | signal | Ready to start playback |
|
|
| Can Play Through | signal | Can play to end without buffering |
|
|
| Play Started | signal | Playback started |
|
|
| Paused | signal | Playback paused |
|
|
| Ended | signal | Playback ended |
|
|
| Seeking | signal | Seek operation started |
|
|
| Seeked | signal | Seek operation completed |
|
|
| Time Updated | signal | Time changed (frequent) |
|
|
| Volume Changed | signal | Volume or mute changed |
|
|
| Rate Changed | signal | Playback rate changed |
|
|
| Entered Fullscreen | signal | Entered fullscreen |
|
|
| Exited Fullscreen | signal | Exited fullscreen |
|
|
| Entered PiP | signal | Entered Picture-in-Picture |
|
|
| Exited PiP | signal | Exited Picture-in-Picture |
|
|
| Error Occurred | signal | Error happened |
|
|
| Buffering Started | signal | Started buffering |
|
|
| Buffering Ended | signal | Finished buffering |
|
|
|
|
### Outputs - Special
|
|
|
|
| Name | Type | Description |
|
|
|------|------|-------------|
|
|
| Captured Frame | string | Base64 data URL of captured frame |
|
|
|
|
---
|
|
|
|
## Implementation Phases
|
|
|
|
### Phase 1: Core Playback (4-6 hours)
|
|
- [ ] Create node file structure
|
|
- [ ] Basic video element with URL support
|
|
- [ ] Play/Pause/Stop signal inputs
|
|
- [ ] Basic state outputs (isPlaying, isPaused, etc.)
|
|
- [ ] Time outputs (currentTime, duration, progress)
|
|
- [ ] Register node in node library
|
|
|
|
### Phase 2: Extended Controls (4-6 hours)
|
|
- [ ] Seek functionality (seekTo, skipForward, skipBackward)
|
|
- [ ] Volume and mute controls
|
|
- [ ] Playback rate control
|
|
- [ ] Loop and autoplay
|
|
- [ ] All time-related event signals
|
|
- [ ] Formatted time outputs
|
|
|
|
### Phase 3: Advanced Features (4-6 hours)
|
|
- [ ] Blob/File support (from File Picker)
|
|
- [ ] Fullscreen API integration
|
|
- [ ] Picture-in-Picture API integration
|
|
- [ ] Frame capture functionality
|
|
- [ ] Start/End time range support
|
|
- [ ] Buffering state and events
|
|
|
|
### Phase 4: Polish & Testing (4-6 hours)
|
|
- [ ] Captions/subtitles support
|
|
- [ ] Cross-browser testing (Chrome, Firefox, Safari, Edge)
|
|
- [ ] Mobile testing (iOS Safari, Android Chrome)
|
|
- [ ] Performance optimization (useDeferredValue for time)
|
|
- [ ] Error handling and edge cases
|
|
- [ ] Documentation
|
|
|
|
---
|
|
|
|
## File Locations
|
|
|
|
### New Files
|
|
```
|
|
packages/noodl-viewer-react/src/nodes/visual/videoplayer.js # Main node
|
|
```
|
|
|
|
### Modified Files
|
|
```
|
|
packages/noodl-viewer-react/src/nodes/index.js # Register node
|
|
packages/noodl-runtime/src/nodelibraryexport.js # Add to UI Elements category
|
|
```
|
|
|
|
### Reference Files (existing patterns)
|
|
```
|
|
packages/noodl-viewer-react/src/nodes/visual/image.js # Similar visual node
|
|
packages/noodl-viewer-react/src/nodes/visual/video.js # Existing basic video (if exists)
|
|
packages/noodl-viewer-react/src/nodes/controls/button.js # Signal input patterns
|
|
```
|
|
|
|
---
|
|
|
|
## Testing Checklist
|
|
|
|
### Manual Testing
|
|
|
|
**Basic Playback**
|
|
- [ ] MP4 URL loads and plays
|
|
- [ ] WebM URL loads and plays
|
|
- [ ] Poster image shows before play
|
|
- [ ] Native controls appear when enabled
|
|
- [ ] Native controls hidden when disabled
|
|
|
|
**Signal Controls**
|
|
- [ ] Play signal starts playback
|
|
- [ ] Pause signal pauses playback
|
|
- [ ] Toggle Play/Pause works correctly
|
|
- [ ] Stop pauses and seeks to 0
|
|
- [ ] Seek To jumps to correct time
|
|
- [ ] Skip Forward/Backward work with Skip Amount
|
|
|
|
**State Outputs**
|
|
- [ ] Is Playing true when playing, false otherwise
|
|
- [ ] Is Paused true when paused
|
|
- [ ] Is Ended true when video ends
|
|
- [ ] Is Buffering true during buffering
|
|
- [ ] Current Time updates during playback
|
|
- [ ] Duration correct after load
|
|
- [ ] Progress 0-1 range correct
|
|
|
|
**Events**
|
|
- [ ] Loaded Metadata fires when ready
|
|
- [ ] Play Started fires on play
|
|
- [ ] Paused fires on pause
|
|
- [ ] Ended fires when complete
|
|
- [ ] Time Updated fires during playback
|
|
|
|
**Advanced Features**
|
|
- [ ] Blob from File Picker plays correctly
|
|
- [ ] Fullscreen enter/exit works
|
|
- [ ] PiP enter/exit works (where supported)
|
|
- [ ] Frame Capture produces valid image
|
|
- [ ] Captions display from VTT file
|
|
- [ ] Start Time auto-seeks on load
|
|
- [ ] End Time auto-pauses/loops
|
|
|
|
**Cross-Browser**
|
|
- [ ] Chrome (latest)
|
|
- [ ] Firefox (latest)
|
|
- [ ] Safari (latest)
|
|
- [ ] Edge (latest)
|
|
- [ ] iOS Safari
|
|
- [ ] Android Chrome
|
|
|
|
**Edge Cases**
|
|
- [ ] Invalid URL shows error state
|
|
- [ ] Network error during playback
|
|
- [ ] Rapid play/pause doesn't break
|
|
- [ ] Seeking while buffering
|
|
- [ ] Source change during playback
|
|
- [ ] Multiple Video Player nodes on same page
|
|
|
|
---
|
|
|
|
## Code Examples for Users
|
|
|
|
### Basic Video Playback
|
|
```
|
|
[Video URL] → [Video Player]
|
|
↓
|
|
[Is Playing] → [If node for UI state]
|
|
```
|
|
|
|
### Custom Controls
|
|
```
|
|
[Button "Play"] → Play signal → [Video Player]
|
|
[Button "Pause"] → Pause signal ↗
|
|
[Slider] → Seek Time + Seek To signal ↗
|
|
↓
|
|
[Current Time] → [Text display]
|
|
[Duration] → [Text display]
|
|
```
|
|
|
|
### Video Upload Preview
|
|
```
|
|
[File Picker] → Blob → [Video Player]
|
|
↓
|
|
[Capture Frame] → [Image node for thumbnail]
|
|
```
|
|
|
|
### E-Learning Progress Tracking
|
|
```
|
|
[Video Player]
|
|
↓
|
|
[Progress] → [Progress Bar]
|
|
[Ended] → [Mark Lesson Complete logic]
|
|
```
|
|
|
|
---
|
|
|
|
## Performance Considerations
|
|
|
|
1. **Time Update Throttling**: The `timeupdate` event fires frequently (4-66Hz). Use `useDeferredValue` to prevent connected nodes from causing frame drops.
|
|
|
|
2. **Blob Memory**: When using blob sources, ensure proper cleanup on source change to prevent memory leaks.
|
|
|
|
3. **Frame Capture**: Canvas operations are synchronous. For large videos, this may cause brief UI freeze. Document this limitation.
|
|
|
|
4. **Multiple Instances**: Test with 3-5 Video Player nodes on same page to ensure no conflicts.
|
|
|
|
---
|
|
|
|
## React 19 Benefits
|
|
|
|
While this node works on React 18.3, React 19 offers:
|
|
|
|
1. **`ref` as prop** - Cleaner implementation without `forwardRef` wrapper
|
|
2. **`useDeferredValue` improvements** - Better time update performance
|
|
3. **`useTransition` for seeking** - Non-blocking seek operations
|
|
|
|
```javascript
|
|
// React 19 pattern for smooth seeking
|
|
const [isPending, startTransition] = useTransition();
|
|
|
|
function handleSeek(time) {
|
|
startTransition(() => {
|
|
videoRef.current.currentTime = time;
|
|
});
|
|
}
|
|
// isPending can drive "Is Seeking" output
|
|
```
|
|
|
|
---
|
|
|
|
## Documentation Requirements
|
|
|
|
After implementation, create:
|
|
- [ ] Node reference page for docs site
|
|
- [ ] Example project: "Video Gallery"
|
|
- [ ] Example project: "Custom Video Controls"
|
|
- [ ] Migration guide from Function-based video handling
|
|
|
|
---
|
|
|
|
## Notes & Gotchas
|
|
|
|
1. **iOS Autoplay**: iOS requires `playsInline` and `muted` for autoplay to work
|
|
2. **CORS**: External videos may need proper CORS headers for frame capture
|
|
3. **HLS/DASH**: May require additional libraries (hls.js, dash.js) - consider Phase 2 enhancement
|
|
4. **Safari PiP**: Has different API than Chrome/Firefox
|
|
5. **Fullscreen**: Different browsers have different fullscreen APIs - use unified helper
|
|
|
|
---
|
|
|
|
## Future Enhancements (Out of Scope)
|
|
|
|
- HLS/DASH streaming support via hls.js
|
|
- Video filters/effects
|
|
- Multiple audio tracks
|
|
- Chapter markers
|
|
- Thumbnail preview on seek (sprite sheet)
|
|
- Analytics integration
|
|
- DRM support
|