Capacitor YouTube Player Plugin
Unofficial Capacitor plugin to embed and control YouTube players on Android, iOS, and Web.[^1]
Features
The Capacitor YouTube Player plugin embeds YouTube videos as inline, frame-positioned players in your Capacitor app. Here are some of the key features:
- 🖥️ Cross-platform: Supports Android, iOS, and Web.
- 🎞️ Inline Playback: Embed players inline on all platforms, including iOS.
- 🧩 Multi-Instance: Create and control multiple players at the same time by ID.
- 📐 Frame Positioning: Position and resize players with CSS pixel frames and keep them in sync with your layout.
- 🎛️ Playback Controls: Load, cue, play, pause, seek, mute, volume, and playback rate.
- 📡 Typed Events: Listen for ready, state, time, rate, error, and fullscreen events with typed listeners.
- 📜 Terms Compliant: Built on the official YouTube IFrame Player API with TOS-compliant plumbing (see YouTube Terms of Service).
- 🤝 Compatibility: Works alongside the Media Session and Screen Orientation plugins.
- 📦 CocoaPods & SPM: Supports CocoaPods and Swift Package Manager for iOS.
- 🔁 Up-to-date: Always supports the latest Capacitor version.
Missing a feature? Just open an issue and we'll take a look!
Use Cases
The YouTube Player plugin is typically used wherever you want to embed YouTube videos in your app, for example:
- Media apps: Embed trailers, music videos, or episodes inline in your content.
- Onboarding & help: Show tutorial and how-to videos directly in your app.
- News & blogs: Render YouTube embeds in articles with native playback performance.
- Education: Build course screens with multiple video lessons on one page.
Compatibility
| Plugin Version | Capacitor Version | Status |
|---|---|---|
| 0.x.x | >=8.x.x | Active support |
Installation
You can use our AI-Assisted Setup to install the plugin. Add the Capawesome Skills to your AI tool using the following command:
npx skills add capawesome-team/skills --skill capacitor-plugins
Then use the following prompt:
Use the `capacitor-plugins` skill from `capawesome-team/skills` to install the `@capawesome/capacitor-youtube-player` plugin in my project.
If you prefer Manual Setup, install the plugin by running the following commands and follow the platform-specific instructions below:
npm install @capawesome/capacitor-youtube-player
npx cap sync
Android
On Android, the plugin uses the android-youtube-player library, an open-source wrapper around the official YouTube IFrame Player API.
Variables
This plugin will use the following project variables (defined in your app's variables.gradle file):
$androidYoutubePlayerVersionversion ofcom.pierfrancescosoffritti.androidyoutubeplayer:core(default:13.1.0)
Permissions
The plugin automatically adds the android.permission.ACCESS_NETWORK_STATE permission to your app's manifest. It is used to recover the player when the network connection is lost and restored. No action is required on your part.
iOS
On iOS, the plugin renders the player in a WKWebView using the official YouTube IFrame Player API. No additional configuration is required.
Web
The web implementation loads the YouTube IFrame Player API from YouTube at runtime. This requires an active network connection. If your app enforces a Content Security Policy (CSP), make sure to allow the YouTube domains (e.g. https://www.youtube.com for script-src and frame-src, and https://www.youtube-nocookie.com for frame-src if you set the host option).
Configuration
No configuration required for this plugin.
Usage
The following examples show how to use the plugin.
Create a player
Measure a placeholder element in your layout and create a player at its position:
import { YoutubePlayer } from '@capawesome/capacitor-youtube-player';
const createPlayer = async () => {
const rect = document
.querySelector('#player-placeholder')
.getBoundingClientRect();
await YoutubePlayer.createPlayer({
id: 'my-player',
frame: {
x: rect.x,
y: rect.y,
width: rect.width,
height: rect.height,
},
videoId: 'dQw4w9WgXcQ',
options: {
mute: true,
},
});
};
Control playback
import { YoutubePlayer } from '@capawesome/capacitor-youtube-player';
const controlPlayback = async () => {
await YoutubePlayer.play({ id: 'my-player' });
await YoutubePlayer.seekTo({ id: 'my-player', seconds: 42 });
await YoutubePlayer.setVolume({ id: 'my-player', volume: 50 });
await YoutubePlayer.setPlaybackRate({ id: 'my-player', rate: 1.5 });
await YoutubePlayer.pause({ id: 'my-player' });
};
Listen for events
import { YoutubePlayer } from '@capawesome/capacitor-youtube-player';
const addListeners = async () => {
await YoutubePlayer.addListener('playerReady', event => {
console.log('Player ready:', event.id);
});
await YoutubePlayer.addListener('playerStateChange', event => {
console.log('Player state:', event.id, event.state);
});
await YoutubePlayer.addListener('playerError', event => {
console.error('Player error:', event.id, event.code);
});
};
Remove a player
import { YoutubePlayer } from '@capawesome/capacitor-youtube-player';
const removePlayer = async () => {
await YoutubePlayer.removePlayer({ id: 'my-player' });
};
API
createPlayer(...)cueVideo(...)getCurrentTime(...)getDuration(...)loadVideo(...)mute(...)pause(...)play(...)removePlayer(...)seekTo(...)setPlaybackRate(...)setPlayerFrame(...)setVolume(...)unmute(...)addListener('currentTimeChange', ...)addListener('fullscreenChange', ...)addListener('playbackRateChange', ...)addListener('playerError', ...)addListener('playerReady', ...)addListener('playerStateChange', ...)removeAllListeners()- Interfaces
- Type Aliases
createPlayer(...)
createPlayer(options: CreatePlayerOptions) => Promise<CreatePlayerResult>
Create a new YouTube player.
On Android and iOS, the player is rendered as a native view that is
positioned above the web view at the given frame. The frame is not
scrolled with the web content. Use setPlayerFrame(...) to keep the
frame in sync with your layout (see the README for a recipe).
The player must be at least 200×200 CSS pixels, as required by the YouTube Terms of Service.
| Param | Type |
|---|---|
options | CreatePlayerOptions |
Returns: Promise<CreatePlayerResult>
Since: 0.1.0
cueVideo(...)
cueVideo(options: CueVideoOptions) => Promise<void>
Load a video into the player without starting playback.
| Param | Type |
|---|---|
options | CueVideoOptions |
Since: 0.1.0
getCurrentTime(...)
getCurrentTime(options: GetCurrentTimeOptions) => Promise<GetCurrentTimeResult>
Get the current playback time of the player.
On Android, the value is answered from the most recent value pushed by the player (updated multiple times per second during playback).
| Param | Type |
|---|---|
options | GetCurrentTimeOptions |
Returns: Promise<GetCurrentTimeResult>
Since: 0.1.0
getDuration(...)
getDuration(options: GetDurationOptions) => Promise<GetDurationResult>
Get the duration of the currently loaded video.
On Android, the value is answered from the most recent value pushed by the player.
| Param | Type |
|---|---|
options | GetDurationOptions |
Returns: Promise<GetDurationResult>
Since: 0.1.0
loadVideo(...)
loadVideo(options: LoadVideoOptions) => Promise<void>
Load a video into the player and start playback.
| Param | Type |
|---|---|
options | LoadVideoOptions |
Since: 0.1.0
mute(...)
mute(options: MuteOptions) => Promise<void>
Mute the player.
| Param | Type |
|---|---|
options | MuteOptions |
Since: 0.1.0
pause(...)
pause(options: PauseOptions) => Promise<void>
Pause playback.
| Param | Type |
|---|---|
options | PauseOptions |
Since: 0.1.0
play(...)
play(options: PlayOptions) => Promise<void>
Start or resume playback.
| Param | Type |
|---|---|
options | PlayOptions |
Since: 0.1.0
removePlayer(...)
removePlayer(options: RemovePlayerOptions) => Promise<void>
Remove the player and release all its resources.
| Param | Type |
|---|---|
options | RemovePlayerOptions |
Since: 0.1.0
seekTo(...)
seekTo(options: SeekToOptions) => Promise<void>
Seek to the given time.
| Param | Type |
|---|---|
options | SeekToOptions |
Since: 0.1.0
setPlaybackRate(...)
setPlaybackRate(options: SetPlaybackRateOptions) => Promise<void>
Set the playback rate of the player.
| Param | Type |
|---|---|
options | SetPlaybackRateOptions |
Since: 0.1.0
setPlayerFrame(...)
setPlayerFrame(options: SetPlayerFrameOptions) => Promise<void>
Update the frame of the player.
Call this method whenever the layout changes (e.g. on scroll, resize or orientation change) to keep the player in sync with your layout.
| Param | Type |
|---|---|
options | SetPlayerFrameOptions |
Since: 0.1.0
setVolume(...)
setVolume(options: SetVolumeOptions) => Promise<void>
Set the volume of the player.
On iOS, the operating system does not allow changing the volume
programmatically, so this method has no effect. Use mute() and
unmute() instead.
| Param | Type |
|---|---|
options | SetVolumeOptions |
Since: 0.1.0
unmute(...)
unmute(options: UnmuteOptions) => Promise<void>
Unmute the player.
| Param | Type |
|---|---|
options | UnmuteOptions |
Since: 0.1.0
addListener('currentTimeChange', ...)
addListener(eventName: 'currentTimeChange', listenerFunc: (event: CurrentTimeChangeEvent) => void) => Promise<PluginListenerHandle>
Called when the current playback time of a player changes.
The event is emitted multiple times per second during playback. The exact frequency depends on the platform.
| Param | Type |
|---|---|
eventName | 'currentTimeChange' |
listenerFunc | (event: CurrentTimeChangeEvent) => void |
Returns: Promise<PluginListenerHandle>
Since: 0.1.0
addListener('fullscreenChange', ...)
addListener(eventName: 'fullscreenChange', listenerFunc: (event: FullscreenChangeEvent) => void) => Promise<PluginListenerHandle>
Called when a player enters or exits fullscreen.
Only available on Android and Web.
| Param | Type |
|---|---|
eventName | 'fullscreenChange' |
listenerFunc | (event: FullscreenChangeEvent) => void |
Returns: Promise<PluginListenerHandle>
Since: 0.1.0
addListener('playbackRateChange', ...)
addListener(eventName: 'playbackRateChange', listenerFunc: (event: PlaybackRateChangeEvent) => void) => Promise<PluginListenerHandle>
Called when the playback rate of a player changes.
On iOS, this event is only emitted for setPlaybackRate(...) calls.
| Param | Type |
|---|---|
eventName | 'playbackRateChange' |
listenerFunc | (event: PlaybackRateChangeEvent) => void |
Returns: Promise<PluginListenerHandle>
Since: 0.1.0
addListener('playerError', ...)
addListener(eventName: 'playerError', listenerFunc: (event: PlayerErrorEvent) => void) => Promise<PluginListenerHandle>
Called when an error occurs in a player.
| Param | Type |
|---|---|
eventName | 'playerError' |
listenerFunc | (event: PlayerErrorEvent) => void |
Returns: Promise<PluginListenerHandle>
Since: 0.1.0
addListener('playerReady', ...)
addListener(eventName: 'playerReady', listenerFunc: (event: PlayerReadyEvent) => void) => Promise<PluginListenerHandle>
Called when a player has finished loading and is ready to receive commands.
| Param | Type |
|---|---|
eventName | 'playerReady' |
listenerFunc | (event: PlayerReadyEvent) => void |
Returns: Promise<PluginListenerHandle>
Since: 0.1.0
addListener('playerStateChange', ...)
addListener(eventName: 'playerStateChange', listenerFunc: (event: PlayerStateChangeEvent) => void) => Promise<PluginListenerHandle>
Called when the state of a player changes.
| Param | Type |
|---|---|
eventName | 'playerStateChange' |
listenerFunc | (event: PlayerStateChangeEvent) => void |
Returns: Promise<PluginListenerHandle>
Since: 0.1.0
removeAllListeners()
removeAllListeners() => Promise<void>
Remove all listeners for this plugin.
Since: 0.1.0
Interfaces
CreatePlayerResult
| Prop | Type | Description | Since |
|---|---|---|---|
id | string | The unique identifier of the created player. | 0.1.0 |
CreatePlayerOptions
| Prop | Type | Description | Since |
|---|---|---|---|
frame | PlayerFrame | The frame of the player in CSS pixels, relative to the viewport. Must be at least 200×200 CSS pixels. | 0.1.0 |
id | string | The unique identifier of the player. If not provided, a random identifier is generated. | 0.1.0 |
options | PlayerOptions | The player options. | 0.1.0 |
videoId | string | The ID of the YouTube video to load into the player. If not provided, the player is created without a video. Load one with loadVideo(...) or cueVideo(...). | 0.1.0 |
web | CreatePlayerWebOptions | Options that are only available on Web. | 0.1.0 |
PlayerFrame
The frame of a player in CSS pixels, relative to the viewport.
| Prop | Type | Description | Since |
|---|---|---|---|
height | number | The height of the player in CSS pixels. Must be at least 200. | 0.1.0 |
width | number | The width of the player in CSS pixels. Must be at least 200. | 0.1.0 |
x | number | The x-coordinate of the player in CSS pixels. | 0.1.0 |
y | number | The y-coordinate of the player in CSS pixels. | 0.1.0 |
PlayerOptions
| Prop | Type | Description | Default | Since |
|---|---|---|---|---|
autoplay | boolean | Whether the video starts playing automatically when the player is created. | false | 0.1.0 |
ccLoadPolicy | boolean | Whether closed captions are shown by default, even if the user has turned captions off. | false | 0.1.0 |
controls | boolean | Whether the player controls are displayed. | true | 0.1.0 |
end | number | The time in seconds at which the player should stop playing the video. | 0.1.0 | |
fullscreen | boolean | Whether the fullscreen button is displayed. Only available on Android and Web. | false | 0.1.0 |
host | string | The host from which the player is loaded. Set to https://www.youtube-nocookie.com to use YouTube's privacy-enhanced mode. Must be either https://www.youtube.com or https://www.youtube-nocookie.com. Note that privacy-enhanced mode does not prevent YouTube from storing cookies on the device, because the IFrame Player API script is always loaded from https://www.youtube.com. | 'https://www.youtube.com' | 0.1.2 |
hl | string | The language of the player interface as an ISO 639-1 two-letter language code or a fully specified locale, such as de or pt-BR. YouTube also uses this language to decide whether automatic captions are shown. If not provided, the player uses the device language. Only available on iOS and Web. | 0.1.4 | |
ivLoadPolicy | boolean | Whether video annotations are shown by default. | false | 0.1.0 |
mute | boolean | Whether the player is muted when it is created. | false | 0.1.0 |
rel | boolean | Whether related videos from other channels are shown when playback ends. If false, related videos are limited to the video's channel. | false | 0.1.0 |
start | number | The time in seconds from which the video should start playing. | 0.1.0 |
CreatePlayerWebOptions
| Prop | Type | Description | Since |
|---|---|---|---|
elementId | string | The ID of an existing DOM element to mount the player into. If provided, the player fills this element instead of being positioned at the given frame, and setPlayerFrame(...) has no effect. Only available on Web. | 0.1.0 |
CueVideoOptions
| Prop | Type | Description | Default | Since |
|---|---|---|---|---|
id | string | The unique identifier of the player. | 0.1.0 | |
startSeconds | number | The time in seconds from which the video should start playing once it is played. | 0 | 0.1.0 |
videoId | string | The ID of the YouTube video to cue. | 0.1.0 |
GetCurrentTimeResult
| Prop | Type | Description | Since |
|---|---|---|---|
currentTime | number | The current playback time in seconds. | 0.1.0 |
GetCurrentTimeOptions
| Prop | Type | Description | Since |
|---|---|---|---|
id | string | The unique identifier of the player. | 0.1.0 |
GetDurationResult
| Prop | Type | Description | Since |
|---|---|---|---|
duration | number | The duration of the currently loaded video in seconds. Returns 0 if no video is loaded. | 0.1.0 |
GetDurationOptions
| Prop | Type | Description | Since |
|---|---|---|---|
id | string | The unique identifier of the player. | 0.1.0 |
LoadVideoOptions
| Prop | Type | Description | Default | Since |
|---|---|---|---|---|
id | string | The unique identifier of the player. | 0.1.0 | |
startSeconds | number | The time in seconds from which the video should start playing. | 0 | 0.1.0 |
videoId | string | The ID of the YouTube video to load. | 0.1.0 |
MuteOptions
| Prop | Type | Description | Since |
|---|---|---|---|
id | string | The unique identifier of the player. | 0.1.0 |
PauseOptions
| Prop | Type | Description | Since |
|---|---|---|---|
id | string | The unique identifier of the player. | 0.1.0 |
PlayOptions
| Prop | Type | Description | Since |
|---|---|---|---|
id | string | The unique identifier of the player. | 0.1.0 |
RemovePlayerOptions
| Prop | Type | Description | Since |
|---|---|---|---|
id | string | The unique identifier of the player. | 0.1.0 |
SeekToOptions
| Prop | Type | Description | Since |
|---|---|---|---|
id | string | The unique identifier of the player. | 0.1.0 |
seconds | number | The time in seconds to seek to. | 0.1.0 |
SetPlaybackRateOptions
| Prop | Type | Description | Since |
|---|---|---|---|
id | string | The unique identifier of the player. | 0.1.0 |
rate | number | The playback rate. Must be one of 0.25, 0.5, 0.75, 1, 1.25, 1.5, 1.75 or 2. | 0.1.0 |
SetPlayerFrameOptions
| Prop | Type | Description | Since |
|---|---|---|---|
frame | PlayerFrame | The new frame of the player in CSS pixels, relative to the viewport. Must be at least 200×200 CSS pixels. | 0.1.0 |
id | string | The unique identifier of the player. | 0.1.0 |
SetVolumeOptions
| Prop | Type | Description | Since |
|---|---|---|---|
id | string | The unique identifier of the player. | 0.1.0 |
volume | number | The volume as a value between 0 and 100. | 0.1.0 |
UnmuteOptions
| Prop | Type | Description | Since |
|---|---|---|---|
id | string | The unique identifier of the player. | 0.1.0 |
PluginListenerHandle
| Prop | Type |
|---|---|
remove | () => Promise<void> |
CurrentTimeChangeEvent
| Prop | Type | Description | Since |
|---|---|---|---|
currentTime | number | The current playback time in seconds. | 0.1.0 |
id | string | The unique identifier of the player. | 0.1.0 |
FullscreenChangeEvent
| Prop | Type | Description | Since |
|---|---|---|---|
fullscreen | boolean | Whether the player is in fullscreen. | 0.1.0 |
id | string | The unique identifier of the player. | 0.1.0 |
PlaybackRateChangeEvent
| Prop | Type | Description | Since |
|---|---|---|---|
id | string | The unique identifier of the player. | 0.1.0 |
rate | number | The new playback rate. | 0.1.0 |
PlayerErrorEvent
| Prop | Type | Description | Since |
|---|---|---|---|
code | PlayerErrorCode | The error code. | 0.1.0 |
id | string | The unique identifier of the player. | 0.1.0 |
PlayerReadyEvent
| Prop | Type | Description | Since |
|---|---|---|---|
id | string | The unique identifier of the player. | 0.1.0 |
PlayerStateChangeEvent
| Prop | Type | Description | Since |
|---|---|---|---|
id | string | The unique identifier of the player. | 0.1.0 |
state | PlayerState | The new state of the player. | 0.1.0 |
Type Aliases
PlayerErrorCode
The error code of a player error.
'html5-error' | 'invalid-parameter' | 'not-embeddable' | 'unknown' | 'video-not-found'
PlayerState
The state of a player.
'buffering' | 'cued' | 'ended' | 'paused' | 'playing' | 'unstarted'
Frame Synchronization
On Android and iOS, the player is a native view that is rendered above the web view. It is not part of the DOM and therefore does not scroll or resize with your web content. Instead, you position it with CSS pixel frames and keep it in sync with your layout by calling setPlayerFrame(...) whenever the layout changes:
import { YoutubePlayer } from '@capawesome/capacitor-youtube-player';
const syncPlayerFrame = async () => {
const rect = document
.querySelector('#player-placeholder')
.getBoundingClientRect();
await YoutubePlayer.setPlayerFrame({
id: 'my-player',
frame: {
x: rect.x,
y: rect.y,
width: rect.width,
height: rect.height,
},
});
};
window.addEventListener('scroll', syncPlayerFrame);
window.addEventListener('resize', syncPlayerFrame);
If you use a framework with its own scroll container (e.g. ion-content), attach the listener to that container's scroll event instead. On the Web, the player is positioned with position: fixed, so the same synchronization code works on all platforms. Alternatively, on the Web you can mount the player into an existing DOM element using the web.elementId option, in which case no frame synchronization is needed.
Since the player is rendered above the web view, HTML content can never overlap the player. Keep this in mind when designing your layout (see also YouTube Terms of Service).
YouTube Terms of Service
By using this plugin, you agree to the YouTube Terms of Service and the YouTube API Services Terms of Service. The plugin is designed to make it easy to comply with the required minimum functionality for embedded players:
- Minimum player size: Embedded players must be at least 200×200 pixels. The plugin enforces this and rejects smaller frames with the
FRAME_INVALIDerror code. - No background playback: Videos must not play in the background. The plugin automatically pauses all players when the app is moved to the background.
- No overlays: The player (including its ads) must not be obscured by other content. Since the native player view is always rendered above the web view, HTML content cannot overlap it by design.
- Correct origin: The plugin sets the
originplayer parameter correctly on all platforms, which avoids embed errors (e.g. error 152/153) without any header workarounds. - No player modification: The plugin embeds the official YouTube player unaltered. Your app must not modify, build upon, or block any portion or functionality of the player — including any ads it serves.
- Branding: If your app displays YouTube logos or other brand assets alongside the player, follow the YouTube Branding Guidelines.
Platform Support
Not every feature is available on all platforms. The following table lists the per-platform differences of the plugin's API:
| Feature | Android | iOS | Web |
|---|---|---|---|
| Playback controls | ✅ | ✅ | ✅ |
fullscreen option | ✅ | ❌ | ✅ |
fullscreenChange event | ✅ | ❌ | ✅ |
| Native mute | ✅ | 🔄 | ✅ |
Additional platform-specific behavior:
- iOS mute emulation: The iOS player library exposes no native mute API, so
mute()is emulated by setting the volume to0.unmute()restores the last volume set viasetVolume(...)(or100). IfsetVolume(...)is called while the player is muted, the volume is only applied on the nextunmute()call. - iOS playback rate event: On iOS, the
playbackRateChangeevent is only emitted forsetPlaybackRate(...)calls, since the iOS player library exposes no playback rate callback. - iOS error mapping: On iOS, videos that are not allowed to be played in embedded players may be reported with the error code
unknowninstead ofnot-embeddable, due to the error mapping of the iOS player library. - Android getters: On Android,
getCurrentTime(...)andgetDuration(...)are answered from the most recent values pushed by the player, which are updated multiple times per second during playback. currentTimeChangefrequency: The event is emitted about 10 times per second on Android and about twice per second on iOS and Web.
FAQ
How is this plugin different from other similar plugins?
This plugin can render multiple native player instances at once, each positioned to an exact frame that you keep in sync with your layout. It exposes a full, strongly typed event set on Android, iOS, and Web, and embeds the official YouTube player unaltered. It is built to comply with the behaviors the YouTube Terms of Service require — a minimum player size, no background playback, nothing drawn over the player, and a correct origin — so you stay compliant by default. It is actively maintained and backed by dedicated support.
Why is my frame rejected with FRAME_INVALID?
Embedded YouTube players must be at least 200×200 CSS pixels, as required by the YouTube Terms of Service. Make sure the width and height of your frame are at least 200.
Why does the player not scroll with my content?
On Android and iOS, the player is a native view rendered above the web view and is not part of the DOM. Use setPlayerFrame(...) to keep it in sync with your layout (see Frame Synchronization).
Can I display HTML content above the player?
No. The native player view is always rendered above the web view. This is also a requirement of the YouTube Terms of Service, which prohibit obscuring the player.
Does playback continue in the background?
No. The YouTube Terms of Service prohibit background playback, so the plugin automatically pauses all players when the app is moved to the background.
Can I use this plugin with Ionic, React, Vue or Angular?
Yes, the plugin is framework-agnostic. It works in any Capacitor app regardless of the web framework, including Ionic with Angular, React, or Vue, as well as plain JavaScript projects.
Related Plugins
- Media Session: Interact with media controllers, volume keys and media buttons.
- Screen Orientation: Lock and unlock the screen orientation, for example to allow landscape playback.
Newsletter
Stay up to date with the latest news and updates about the Capawesome, Capacitor, and Ionic ecosystem by subscribing to our Capawesome Newsletter.
Changelog
See CHANGELOG.md.
License
See LICENSE.
[^1]: This project is not affiliated with, endorsed by, sponsored by, or approved by Google LLC or YouTube LLC or any of their affiliates or subsidiaries. "YouTube" is a trademark of Google LLC.