Skip to content

Commit 05a55ec

Browse files
feat: implement Airplay support
1 parent a42a49a commit 05a55ec

39 files changed

+9982
-7617
lines changed

β€Ž@types/global.d.tsβ€Ž

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
import type { RemotePlaybackState, WebKitPlaybackTargetAvailabilityEvent } from './remote-playback';
2+
3+
interface RemotePlayback extends EventTarget {
4+
readonly state: RemotePlaybackState;
5+
watchAvailability(callback: (available: boolean) => void): Promise<number>;
6+
cancelWatchAvailability(id?: number): Promise<void>;
7+
prompt(): Promise<void>;
8+
}
9+
10+
interface HTMLVideoElement {
11+
readonly remote?: RemotePlayback;
12+
}
13+
14+
declare global {
15+
interface Window {
16+
WebKitPlaybackTargetAvailabilityEvent?: {
17+
prototype: WebKitPlaybackTargetAvailabilityEvent;
18+
new(type: string, eventInitDict?: EventInit): WebKitPlaybackTargetAvailabilityEvent;
19+
};
20+
}
21+
}

β€Ž@types/remote-playback.tsβ€Ž

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
export type RemotePlaybackState = 'connecting' | 'connected' | 'disconnected';
2+
3+
export interface RemotePlaybackAvailabilityEvent extends Event {
4+
availability: 'available' | 'not-available';
5+
}
6+
7+
export interface RemotePlaybackPlugin {
8+
getState(): RemotePlaybackState | null;
9+
isConnected(): boolean;
10+
}
11+
12+
export interface WebKitPlaybackTargetAvailabilityEvent extends Event {
13+
availability: 'available' | 'not-available';
14+
}
15+
16+
export interface HTMLVideoElementWithAirPlay extends HTMLVideoElement {
17+
webkitCurrentPlaybackTargetIsWireless: boolean;
18+
webkitShowPlaybackTargetPicker(): void;
19+
}
File renamed without changes.

β€Ž@types/videojs.tsβ€Ž

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
import videojs from '@silvermine/video.js';
2+
import type { VideoJsPlayer as VjsPlayer } from 'video.js';
3+
import type { AirPlayManager } from '../src/js/airplay/interfaces/Airplay.interfaces';
4+
import type { RemotePlaybackPlugin } from '../src/RemotePlaybackPlugin';
5+
6+
export type VideoJs = typeof videojs;
7+
8+
export interface VideoJsPlayer extends VjsPlayer {
9+
airPlay?: AirPlayManager;
10+
remotePlayback?: RemotePlaybackPlugin;
11+
}
12+
13+
/**
14+
* Video.js Button component interface
15+
*/
16+
export interface VideoJsButton {
17+
el(): Element;
18+
show(): void;
19+
hide(): void;
20+
addClass(className: string): void;
21+
removeClass(className: string): void;
22+
controlText(text?: string): string | void;
23+
localize(text: string): string;
24+
on(event: string, callback: () => void): void;
25+
buildCSSClass(): string;
26+
}
27+
28+
/**
29+
* Video.js Button constructor interface
30+
*/
31+
export interface VideoJsButtonConstructor {
32+
new (player: VideoJsPlayer, options?: Record<string, unknown>): VideoJsButton;
33+
}

β€ŽREADME.mdβ€Ž

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,56 @@ for casting to external devices. This plugin bridges that gap by:
2424
* **Future-Proofing**: Built with modern web standards and TypeScript for
2525
maintainability
2626

27+
## Development
28+
29+
### Building the Plugin
30+
31+
```bash
32+
npm run build
33+
```
34+
35+
This command:
36+
37+
1. **Compiles TypeScript**: Builds the source code using Vite
38+
2. **Generates Bundles**: Creates both UMD and ESM versions
39+
3. **Copies Assets**: Automatically copies icons from `src/images/` to `dist/images/`
40+
41+
### Development Server
42+
43+
For interactive testing during development, use the built-in dev server:
44+
45+
```bash
46+
# Build the plugin and start the dev server
47+
npm run dev:build
48+
49+
# Or start the dev server separately (requires pre-built plugin)
50+
npm run build
51+
npm run dev
52+
```
53+
54+
The dev server provides:
55+
56+
* **Interactive Test Pages**: Browse to `http://localhost:3000` for test examples
57+
* **Live Testing**: Test AirPlay, Chromecast, and Remote Playback API functionality
58+
* **Browser-Specific Tests**: Separate pages for testing different casting technologies
59+
* **Real-time Debugging**: Console logging and status updates for development
60+
61+
#### Available Test Pages
62+
63+
* **`/`** - Main examples index with links to all test pages
64+
* **`/airplay.html`** - AirPlay functionality testing (Safari/Apple devices)
65+
* **`/chromecast.html`** - Chromecast functionality testing (Chrome browser)
66+
* **`/remote-playback.html`** - Generic Remote Playback API testing (cross-browser)
67+
68+
### Running Tests
69+
70+
```bash
71+
# Run unit tests
72+
npm test
73+
74+
# Run linting and standards checks
75+
npm run standards
76+
```
2777

2878
## License
2979

β€Žexamples/README.mdβ€Ž

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
# Test Examples
2+
3+
This directory contains interactive test pages for the Video.js Remote Playback plugin.
4+
5+
## Usage
6+
7+
Start the development server:
8+
9+
```bash
10+
npm run dev:build
11+
```
12+
13+
Then browse to `http://localhost:3000` to access the test pages.
14+
15+
## Test Pages
16+
17+
### Main Index (`/`)
18+
19+
Overview page with links to all test examples and usage instructions.
20+
21+
### AirPlay Test (`/airplay.html`)
22+
23+
* **Purpose**: Test AirPlay functionality
24+
* **Browser**: Safari (macOS/iOS)
25+
* **Requirements**: AirPlay-compatible devices on the same network
26+
* **Expected**: AirPlay button appears in Video.js control bar
27+
28+
### Chromecast Test (`/chromecast.html`)
29+
30+
* **Purpose**: Test Chromecast functionality
31+
* **Browser**: Chrome
32+
* **Requirements**: Chromecast devices on the same network
33+
* **Expected**: Chromecast button appears in Video.js control bar
34+
35+
### Remote Playback API Test (`/remote-playback.html`)
36+
37+
* **Purpose**: Test generic Remote Playback API across browsers
38+
* **Browser**: Any (shows different behavior per browser)
39+
* **Expected**: Shows browser compatibility and API availability info
40+
41+
## Development Notes
42+
43+
* All test pages use CDN versions of Video.js for simplicity
44+
* Plugin assets are served from the `/dist/` directory via Vite's `publicDir`
45+
* Console logging provides detailed debugging information
46+
* Real-time status updates help track plugin behavior

β€Žexamples/airplay.htmlβ€Ž

Lines changed: 126 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,126 @@
1+
<!DOCTYPE html>
2+
<html lang="en">
3+
<head>
4+
<meta charset="UTF-8">
5+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
6+
<title>AirPlay Test - Video.js Remote Playback Plugin</title>
7+
<!-- Video.js CSS from CDN for dev server -->
8+
<link href="https://vjs.zencdn.net/7.19.0/video-js.css" rel="stylesheet">
9+
<!-- Plugin CSS served from dist -->
10+
<link href="/videojs-remoteplayback.css" rel="stylesheet">
11+
<style>
12+
body {
13+
font-family: Arial, sans-serif;
14+
max-width: 800px;
15+
margin: 0 auto;
16+
padding: 20px;
17+
}
18+
.info {
19+
background: #e3f2fd;
20+
padding: 15px;
21+
border-radius: 5px;
22+
margin: 20px 0;
23+
}
24+
.video-container {
25+
margin: 20px 0;
26+
}
27+
</style>
28+
</head>
29+
<body>
30+
<div class="video-container">
31+
<video
32+
id="airplay-test-player"
33+
class="video-js vjs-default-skin"
34+
controls
35+
preload="auto"
36+
width="640"
37+
height="360"
38+
data-setup="{}">
39+
<source src="https://commondatastorage.googleapis.com/gtv-videos-bucket/sample/BigBuckBunny.mp4" type="video/mp4">
40+
<p class="vjs-no-js">
41+
To view this video please enable JavaScript, and consider upgrading to a web browser that
42+
<a href="https://videojs.com/html5-video-support/" target="_blank">supports HTML5 video</a>.
43+
</p>
44+
</video>
45+
</div>
46+
47+
<!-- Video.js from CDN for dev server -->
48+
<script src="https://vjs.zencdn.net/7.19.0/video.min.js"></script>
49+
<!-- Plugin JS served from dist -->
50+
<script src="/videojs-remoteplayback.umd.js"></script>
51+
<script>
52+
// Debug: Check what's available
53+
console.log('πŸ” Available globals:', Object.keys(window).filter(key => key.includes('Video')));
54+
console.log('πŸ” VideoJsRemotePlayback:', typeof VideoJsRemotePlayback);
55+
56+
// Initialize the plugin
57+
if (typeof VideoJsRemotePlayback === 'function') {
58+
VideoJsRemotePlayback(videojs);
59+
} else {
60+
console.error('❌ VideoJsRemotePlayback is not available as a global function');
61+
}
62+
63+
// Create the player
64+
const player = videojs('airplay-test-player', {
65+
fluid: true,
66+
responsive: true
67+
});
68+
69+
// Add click event listener to test button functionality
70+
player.ready(() => {
71+
setTimeout(() => {
72+
const airplayButton = document.querySelector('.vjs-airplay-button');
73+
if (airplayButton) {
74+
airplayButton.addEventListener('click', () => {
75+
console.log('🎯 AirPlay button clicked!');
76+
console.log('πŸ” Player airPlay manager:', player.airPlay);
77+
});
78+
console.log('βœ… Added click listener to AirPlay button');
79+
}
80+
}, 200);
81+
});
82+
83+
// Check for duplicate icons
84+
player.ready(() => {
85+
setTimeout(() => {
86+
const airplayButton = player.el().querySelector('.vjs-airplay-button');
87+
if (airplayButton) {
88+
const iconPlaceholders = airplayButton.querySelectorAll('.vjs-icon-placeholder');
89+
console.log('πŸ” Icon placeholders found:', iconPlaceholders.length);
90+
console.log('βœ… Should be exactly 1 icon placeholder');
91+
console.log('Button HTML:', airplayButton.outerHTML);
92+
93+
if (iconPlaceholders.length === 1) {
94+
console.log('πŸŽ‰ SUCCESS: Only 1 icon placeholder - duplication fixed!');
95+
96+
// Check if using the correct SVG icon
97+
const iconStyle = window.getComputedStyle(iconPlaceholders[0]);
98+
const backgroundImage = iconStyle.backgroundImage;
99+
console.log('πŸ” Background image URL:', backgroundImage);
100+
101+
if (backgroundImage.includes('ic_airplay_white_24px.svg')) {
102+
console.log('βœ… Using correct AirPlay SVG icon compiled from SCSS via Vite!');
103+
console.log('🎯 Icon is loaded from bundled images (built with the plugin)');
104+
console.log('πŸ“¦ CSS compiled from styles/airplay.scss via Vite build process');
105+
106+
// Try to determine the actual path
107+
if (backgroundImage.includes('dist/images/')) {
108+
console.log('🌐 Icon path: dist/images/ic_airplay_white_24px.svg');
109+
} else if (backgroundImage.includes('images/')) {
110+
console.log('🌐 Icon path: images/ic_airplay_white_24px.svg');
111+
}
112+
} else {
113+
console.log('⚠️ Icon might not be loading from the correct path');
114+
console.log('Background image:', backgroundImage);
115+
}
116+
} else {
117+
console.log('❌ ISSUE: Found', iconPlaceholders.length, 'icon placeholders');
118+
}
119+
} else {
120+
console.log('❌ AirPlay button not found (correct for non-Safari browsers)');
121+
}
122+
}, 100);
123+
});
124+
</script>
125+
</body>
126+
</html>

0 commit comments

Comments
Β (0)