A native Titanium module that enables inline YouTube video playback without forcing the native OS player.
- Built with YouTubePlayerKit for iOS.
- Built with android-youtube-player for Android.
- ✅ Inline playback (no forced fullscreen)
- ✅ Configurable autoplay and loop
- ✅ Mute/unmute control
- ✅ Video quality control (Youtube is ignoring this for now)
- ✅ Adjustable playback speed (0.25x - 2x)
- ✅ Seek to any point in the video
- ✅ Detailed state and metadata events
- ✅ No native controls (Optional)
- ✅ Caption support
- ✅ Modern async API
- Titanium SDK 13.0.0+
Download the latest version from the releases page.
# Copy the compiled module to:
{YOUR_PROJECT}/modules/iphone/Add the module to your tiapp.xml:
<modules>
<module>ti.youtubeplayer</module>
</modules>Add microphone permission to tiapp.xml for recording:
<ios>
<plist>
<dict>
<key>NSAllowsArbitraryLoadsInWebContent</key>
<true/>
</dict>
</plist>
</ios>constYouTubePlayer=require('ti.youtubeplayer');constplayer=YouTubePlayer.createPlayerView({videoId: 'dQw4w9WgXcQ',autoplay: true,loop: true,showControls: false,muted: true,preferredQuality: YouTubePlayer.PLAYBACK_QUALITY_HIGH_RESOLUTION,width: Ti.UI.FILL,height: 300,backgroundColor: '#000'});win.add(player);| Property | Type | Default | Description |
|---|---|---|---|
videoId | String | required | YouTube video ID |
scalingMode | String | true | 'SCALING_ASPECT_FIT' or 'SCALING_ASPECT_FILL' |
loop | Boolean | true | Loop playback |
showControls | Boolean | false | Show YouTube controls |
muted | Boolean | true | Start muted |
showCaptions | Boolean | false | Show captions |
showFullscreenButton | Boolean | false | Show fullscreen button |
preferredQuality | String | 'hd1080' | Preferred quality ('small', 'medium', 'large', 'hd720', 'hd1080', 'highres') |
autoplay | Boolean | true | Start playback automatically |
Note: All standard Ti.UI.View properties also work (width, height, top, left, backgroundColor, etc.)
- PLAYBACK_QUALITY_AUTO
- PLAYBACK_QUALITY_SMALL
- PLAYBACK_QUALITY_MEDIUM
- PLAYBACK_QUALITY_HD720
- PLAYBACK_QUALITY_HD1080
- PLAYBACK_QUALITY_HIGH_RESOLUTION
- SCALING_ASPECT_FILL
- SCALING_ASPECT_FIT
Starts video playback.
player.play();Pauses video playback.
player.pause();Stops video playback completely.
player.stop();Mutes the video audio.
player.mute();Unmutes the video audio.
player.unmute();Returns whether the player is muted.
constmuted=player.isMuted();Ti.API.info('Muted: '+muted);Returns:Boolean
Seeks to a specific point in the video.
// Jump to 30 secondsplayer.seek(30);// Jump to 1 minute 30 secondsplayer.seek(90);Parameters:
seconds(Number): Position in seconds
Gets the total video duration.
player.getDuration(function(e){Ti.API.info('Duration: '+e.duration+' seconds');});Callback returns:
duration(Number): Duration in seconds
Gets the current playback time.
player.getCurrentTime(function(e){Ti.API.info('Current time: '+e.currentTime+' seconds');});Callback returns:
currentTime(Number): Current time in seconds
Sets the playback speed.
// Normal speedplayer.setPlaybackRate(1.0);// 1.5x fasterplayer.setPlaybackRate(1.5);// 0.5x slowerplayer.setPlaybackRate(0.5);Parameters:
rate(Number): Speed (valid values:0.25,0.5,0.75,1.0,1.25,1.5,1.75,2.0)
Changes the current video.
player.changeVideo('dQw4w9WgXcQ');Parameters:
videoId(String): New video ID
Loads and plays a new video.
player.loadVideo({videoId: 'dQw4w9WgXcQ',startSeconds: 10// Optional: start at 10 seconds});Parameters:
videoId(String): Video IDstartSeconds(Number, optional): Start time in seconds
Loads a video without starting playback.
player.cueVideo({videoId: 'dQw4w9WgXcQ',startSeconds: 10// Optional});Parameters:
videoId(String): Video IDstartSeconds(Number, optional): Start time in seconds
Reloads the current player.
player.reload();Sets playback quality (not guaranteed by YouTube).
player.setPlaybackQuality('hd1080');// orplayer.setPlaybackQuality(player.PLAYBACK_QUALITY_HD1080);Gets available qualities for the current video.
player.getAvailableQualityLevels(function(e){Ti.API.info('Available qualities: '+JSON.stringify(e.levels));});Callback returns:
levels(Array): List of available qualities
Fired when the overall player state changes.
player.addEventListener('playerStateChange',function(e){Ti.API.info('Player state: '+e.playerState);});Event properties:
playerState(String):'idle','ready','error'
Fired when the playback state changes.
player.addEventListener('playbackStateChange',function(e){Ti.API.info('State: '+e.state);Ti.API.info('Code: '+e.code);Ti.API.info('Is ready: '+e.isFullyReady);});Event properties:
state(String):'unstarted','ended','playing','paused','buffering','cued'code(Number):-1(unstarted),0(ended),1(playing),2(paused),3(buffering),5(cued)isFullyReady(Boolean): Indicates if the player is completely ready to receive commands
Fired when playback quality changes.
player.addEventListener('playbackQualityChange',function(e){Ti.API.info('Quality: '+e.quality);});Fired when playback speed changes.
player.addEventListener('playbackRateChange',function(e){Ti.API.info('Playback rate: '+e.rate);});Event properties:
rate(Number): Current playback rate
Fired when video metadata is loaded.
player.addEventListener('metadataReceived',function(e){Ti.API.info('Title: '+e.title);Ti.API.info('Author: '+e.author);Ti.API.info('Video ID: '+e.videoId);});Event properties:
title(String): Video titleauthor(String): Channel namevideoId(String): Video ID
Fired when mute state changes.
player.addEventListener('muteChanged',function(e){Ti.API.info('Muted: '+e.muted);});Event properties:
muted(Boolean):trueif muted,falseif unmuted
Fired when an error occurs.
player.addEventListener('error',function(e){Ti.API.error('Error: '+e.message);Ti.API.error('Code: '+e.code);});Event properties:
message(String): Error messagecode(Number): Error codetype(String): Error type
| Code | Type | Message |
|---|---|---|
| 2 | invalid_parameter | Invalid parameter value (e.g., invalid video ID) |
| 5 | html5_error | HTML5 player error |
| 8 | video_removed | Video has been removed or flagged as inappropriate |
| 100 | not_found | Video not found, private, or age-restricted |
| 101 | embedding_disabled | Owner doesn't allow embedding |
| 150 | embedding_disabled | Same as 101 (duplicate) |
| 153 | missing_referer | Missing HTTP Referer header or API Client identification |
constYouTubePlayer=require('ti.youtubeplayer');constwin=Ti.UI.createWindow({backgroundColor: '#fff'});constplayer=YouTubePlayer.createPlayerView({videoId: 'dQw4w9WgXcQ',autoplay: true,loop: true,muted: true,width: Ti.UI.FILL,height: 300,top: 0});constmuteButton=Ti.UI.createButton({title: '🔇',width: 50,height: 50,right: 10,top: 10,backgroundColor: '#000',opacity: 0.7,borderRadius: 25});muteButton.addEventListener('click',function(){if(player.isMuted()){player.unmute();}else{player.mute();}});player.addEventListener('muteChanged',function(e){muteButton.title=e.muted ? '🔇' : '🔊';});win.add(player);win.add(muteButton);win.open();constYouTubePlayer=require('ti.youtubeplayer');constvideos=['dQw4w9WgXcQ','kJQP7kiw5Fk','L_jWHffIx5E'];letcurrentIndex=0;constplayer=YouTubePlayer.createPlayerView({videoId: videos[0],autoplay: true,loop: false,width: Ti.UI.FILL,height: 300});player.addEventListener('playbackStateChange',function(e){if(e.state==='ended'){currentIndex=(currentIndex+1)%videos.length;player.changeVideo(videos[currentIndex]);}});win.add(player);constYouTubePlayer=require('ti.youtubeplayer');constplayer=YouTubePlayer.createPlayerView({videoId: 'dQw4w9WgXcQ',autoplay: false,showControls: false,width: Ti.UI.FILL,height: 300});constcontrolsView=Ti.UI.createView({height: 60,bottom: 0,backgroundColor: 'rgba(0,0,0,0.7)'});constplayButton=Ti.UI.createButton({title: '▶️',left: 10,width: 50});constpauseButton=Ti.UI.createButton({title: '⏸',left: 70,width: 50});constprogressLabel=Ti.UI.createLabel({text: '0:00 / 0:00',right: 10,color: '#fff'});playButton.addEventListener('click',function(){player.play();});pauseButton.addEventListener('click',function(){player.pause();});// Update progress every secondsetInterval(function(){player.getCurrentTime(function(e){player.getDuration(function(d){constcurrent=Math.floor(e.currentTime);consttotal=Math.floor(d.duration);progressLabel.text=formatTime(current)+' / '+formatTime(total);});});},1000);functionformatTime(seconds){constmins=Math.floor(seconds/60);constsecs=seconds%60;returnmins+':'+(secs<10 ? '0' : '')+secs;}controlsView.add(playButton);controlsView.add(pauseButton);controlsView.add(progressLabel);win.add(player);win.add(controlsView);constYouTubePlayer=require('ti.youtubeplayer');constplayer=YouTubePlayer.createPlayerView({videoId: 'dQw4w9WgXcQ',autoplay: true,muted: true,width: Ti.UI.FILL,height: 300});player.addEventListener('playbackStateChange',function(e){// Wait for player to be fully ready before executing commandsif(e.state==='playing'){Ti.API.info('Player is ready! Safe to call commands now.');// Now it's safe to unmuteplayer.unmute();// Or change speedplayer.setPlaybackRate(1.5);}});win.add(player);If you want to persist the mute state between app sessions:
constYouTubePlayer=require('ti.youtubeplayer');// Load saved mute stateconstsavedMuteState=Ti.App.Properties.getBool('my_youtube_muted',true);constplayer=YouTubePlayer.createPlayerView({videoId: 'dQw4w9WgXcQ',muted: savedMuteState,width: Ti.UI.FILL,height: 300});// Save mute state when it changesplayer.addEventListener('muteChanged',function(e){Ti.App.Properties.setBool('my_youtube_muted',e.muted);Ti.API.info('Mute state saved: '+e.muted);});win.add(player);YouTube decides quality based on connection. You can suggest a quality:
constplayer=YouTubePlayer.createPlayerView({videoId: 'VIDEO_ID',preferredQuality: 'hd1080'// or 'highres' for 4K});Contributions are welcome! Please feel free to submit a Pull Request.
- Fork the repository
- Create your feature branch (
git checkout -b feature/amazing-feature) - Commit your changes (
git commit -m 'Add some amazing feature') - Push to the branch (
git push origin feature/amazing-feature) - Open a Pull Request

