Quest 6: Adding YouTube API

OLD STUFF

The only way I can get YouTube API to work at all is to add code to the existing 'page.html' file to load everything.

I also have to include two or three urls in the CSP section at the top of 'page.html'. Adding something to the CSP via Javascript technically changes the data, but it does not actually update the CSP. I believe that is a security feature, and Pixie has seconded that.

Here's my modified 'page.html':

page.html
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="utf-8"/>
  <title>A Test Page</title>
  <!--<link rel="shortcut icon" type="image/png" href="images/favicon.png"/>-->
  <meta http-equiv="Content-Security-Policy" content="
    default-src 'self';
    style-src 'unsafe-inline' 'self';
    font-src 'self';
    script-src 'unsafe-inline' 'self' https://www.youtube.com/ https://www.youtube.com/iframe_api; 
    img-src 'self';
    frame-src 'unsafe-inline' 'self' https://www.youtube.com/;" //KV added youtube to 2 lines for youtube iframe and API capability
  >
  <meta name="viewport" content="width=device-width, initial-scale=1.0">  <!-- KV ADDED THIS LINE FOR MOBILE STYLING -->
  <script src="game/rhfiles/jquerystuff/jquery-3.5.1.min.js"></script> <!-- KV MODDED THIS LINE. JQUERY FILES ARE IN game/rhfiles/jquerystuff -->
  <link rel="stylesheet" href="game/rhfiles/jquerystuff/jquery-ui-1.12.1/jquery-ui-smoothness.css"> <!-- KV MODDED THIS LINE. JQUERY FILES ARE IN game/rhfiles/jquerystuff -->
  <script src="game/rhfiles/jquerystuff/jquery-ui-1.12.1/jquery-ui.min.js"></script> <!-- KV MODDED THIS LINE. JQUERY FILES ARE IN game/rhfiles/jquerystuff -->
  
  <script src="lib/_util.js"></script>
  <script src="lib/_settings.js"></script>
  <script>
    const FOLDER = "game";
    document.writeln('<script src="' + FOLDER + '/settings.js"></scr' + "ipt>"); 
  </script>
  <script>
    settings.writeScript(FOLDER)
  </script>
  
</head>



<body>

<div id="main">
<div id="output"></div>
<div id="input"></div>
</div>

<script>
  io.createPanes();
</script>

<div id="dialog"></div>
<div id="quest-map"></div>
<div id="quest-image"></div>

	<!-- KV added YouTubeAPI  -->
	
	<!-- 1. The <iframe> (and video player) will replace this <div> tag. -->
    <div id="youtube"></div>

    <script>
      // 2. This code loads the IFrame Player API code asynchronously.
      var tag = document.createElement('script');

      tag.src = "https://www.youtube.com/iframe_api";
      var firstScriptTag = document.getElementsByTagName('script')[0];
      firstScriptTag.parentNode.insertBefore(tag, firstScriptTag);

      // 3. This function creates an <iframe> (and YouTube player)
      //    after the API code downloads.
      var ytPlayer;
      function CreateYouTube(vid='7vIi0U4rSX4',
								ht='390',
								wid='640',
								vars={ 'autoplay': 1 },
								evts={
									'onReady':onPlayerReady,
									'onStateChange':onPlayerStateChange
								}) {
        ytPlayer = new YT.Player('youtube', {
          height: ht,
          width: wid,
          videoId: vid,
          playerVars: vars,
          events: evts
        });
      }
	  function createYouTube(...args){
		  CreateYouTube(...args)
	  }
	  
      // 4. The API will call this function when the video player is ready.
      function onPlayerReady(event) {
        event.target.playVideo();
        
      }

      // 5. The API calls this function when the player's state changes.
      //    The function indicates that when playing a video (state=1),
      //    the player should play for six seconds and then stop.
      var done = false;
      function onPlayerStateChange(event) {
        if (event.data == YT.PlayerState.PLAYING && !done) {
          //setTimeout(stopYouTube, 6000);
          done = true;
        }
      }
      $('#youtube').hide();
    </script>
	
	<!--END OF YOUTUBE API -->

</body>
</html>



I have to run the following code from settings.setup. Otherwise, issues arise (meaning this code doesn't do jack squat, presumably because everything hasn't loaded yet, which makes it hard to change things which do not yet exist).

Code added to settings.setup
		//================================================
		//Clean up the YouTube player div
		var elem = $("#youtube");
		elem.insertBefore($("#input"));
		elem.hide();
		settings.youTubeElem = function(){return $("#youtube")};

		//-------------------------------------------------


I added some YouTube functions (mostly redundant):

YouTube Functions

//==================
//YouTube functions|
//==================

//Alex Warren's Quest video id  -->  7vIi0U4rSX4

/*
 //OLD FUNCTION as of v0.9
function youTube(id='7vIi0U4rSX4',auto=true){
	killYoutube();
	var autoplay = '1';
	if (!auto) autoplay = '0';
	var s = '<iframe id="youTube" width="560" height="315"';
	s += ' src="https://www.youtube.com/embed/'+id+'?autoplay='+autoplay+'"';
	s += ' frameborder="0" allowfullscreen></iframe>';
	msg(s);
	
};
*/


function setYouTubeVolume(vol){
	ytPlayer.setVolume(vol);
};

function muteYouTube(){
	var ytp = $("#youtube");
	if (ytp.length){
		ytPlayer.mute();
		return 1;
	}
	return 0;
};

function unmuteYouTube(){
	var ytp = $("#youtube");
	if (ytp.length && ytPlayer.isMuted()){
		ytPlayer.unMute();
		return 1;
	}
	return 0;
	
};


function pauseYouTube(){
	ytPlayer.pauseVideo();
};

function playYouTube(){
	ytPlayer.playVideo();
};

function stopYouTube(){
	ytPlayer.stopVideo();
};

function hideYouTube(){
	var ytp = $("#youtube");
	ytp.hide();
};

function showYouTube(){
	var ytp = $("#youtube");
	ytp.show();
};

function loadYouTubeVideoById(vid){
	ytPlayer.loadVideoById(vid);
};

function killYoutube(){
	//Returns 1 if an element is found.
	//Returns 0 if no element is found.
	if ($('#youtube').length){
		$('#youtube').remove();
		return 1;
	}
	return 0;
};


function getYouTubeId(){
	var ytpId = '';
	if (typeof(ytPlayer.getVideoData)!=='undefined'){
		ytpId = ytPlayer.getVideoData().video_id;
	}
	return ytpId;
};
//=========================
//END OF YouTube functions|
//=========================



Finally, I just had to add functions to the TV, the first room, and the second room.

GAME OBJECTS with YouTube API calls
createRoom("cellar", {
  desc:"The cellar is small, dimly lit, and dingy.{once:  It sure is nice of XanMag to let you and Ralph stay here, though!}",
  up:new Exit("stairway"),
  afterEnter:function(){
	if (w.TV.switchedon){
		//msg("Ralph turns the TV off on the way out.");
		//w.TV.doSwitchoff();
		showYouTube();
		ytPlayer.setVolume('100');
	} 
  },
  onExit:function(){
	if (w.TV.switchedon){
		//msg("Ralph turns the TV off on the way out.");
		//w.TV.doSwitchoff();
		hideYouTube();
		ytPlayer.setVolume('10');
		msg("You can still hear the TV, but just barely.");
	} 
  },
});


createItem("TV", SWITCHABLE(false), {
	loc:"cellar",
	examine:"An ordinary TV, currently{if:TV:switchedon: tuned to Quest TV: switched off}.<br><br>The remote is not here.\
	  So, all you can do is switch it on or off.",
	onSwitchOn:function(){
		if (!w.TV.switchedOnTimes){
			w.TV.switchedOnTimes = 0;
			createYouTube('7vIi0U4rSX4',390,640,{ 'playlist': '7vIi0U4rSX4',
									'autoplay': 1,
									'loop':1,
									'rel':0 });
		}else{
			var cvid;
			cvid  = getYouTubeId();
			if (cvid !== '7vIi0U4rSX4' ){
				loadYouTubeVideoById('7vIi0U4rSX4');
				//playYouTube()
			}else{
				playYouTube();
				showYouTube();
			}
		}
		w.TV.switchedOnTimes++;
		msg("XM has it on Quest TV, and he has hidden the remote.");
		showYouTube();
		
		if (w.TV.switchedOffTimes===1){
			//msg ("The video starts over every time you turn it off and back on.");
		}
	},
	onSwitchOff:function(){
		if (!w.TV.switchedOffTimes){
			w.TV.switchedOffTimes = 0;
			msg ("{b:{i:[SYSTEM MESSAGE:  THANK YOU!]}}");
		}
		w.TV.switchedOffTimes++;
		pauseYouTube();
		hideYouTube();
	},
});



createRoom("stairway", {
	desc:"A narrow stair.",
	down:new Exit("cellar"),
	up:new Exit("Kitchen"),
	afterEnter:function(){
		if (w.TV.switchedon && game.player.previousLoc==="Kitchen"){
			//msg("Ralph turns the TV off on the way out.");
			//w.TV.doSwitchoff();
			ytPlayer.setVolume('10');
			msg("You can hear muffled sounds from the TV.");
		} 
	},
	onExit:function(){
		if (w.TV.switchedon && game.player.previousLoc==="cellar"){
			//msg("Ralph turns the TV off on the way out.");
			//w.TV.doSwitchoff();
			ytPlayer.setVolume('0');
		} 
	},
});


CSS settings to adjust the iframe's width on mobile.

(Only the iframe#youtube bit is relevant here.)

CSS Settings
/* Added by KV for MOBILE */
@media only screen and (max-width: 956px) {
	#panes {
		display: none !important; /* Hide side panes if width less than 956px (on mobile device). */ /* Unnecessary after QuestJS 0.2  */
	}
	#status {
		width:100% !important;
	}
	#main {
		margin-left: 0px;
		margin-right: 0px;
		padding: 0px 0px;
	}
	input, select, textarea { /* Stop ZOOM during input  */
		font-size: 16px;
	}
	iframe#youtube {
		width:auto;
		height:auto;
	}
}

NOTE

This code will most assuredly get updated. In fact, I just updated quite a bit of it before posting it here.


Here is a link to this "test" game's title page, if anyone feels like trying to break something:

Quest 6 Game 1


Happy gaming!


EDIT

New info is down the page


UPDATE

The video will start by itself in a mobile browser as long as you set it to play from the onPlayerReady function.


Here's my first attempt at documentation:

https://gist.github.com/KVonGit/7934e8a7a6ac26fd7edcd6dda7f0bc04


Comments and suggestions are welcomed.


Thanks, I have linked to that from the QuestJS docs.


When you turn on the TV, the video starts below it. If you continue playing, new text is displayed above the video. Is it possible to have the video scroll up just like normal text?


Is it possible to have the video scroll up just like normal text?

Behold, the last two lines of youTubeAPISetup.js:

elem.insertBefore($("#input"));
  elem.hide();

You could probably change that to:

$("#output").append(elem)
//elem.hide()
You'd probably not want to hide the youtube element in this scenario.

Also, I'm not sure if clearing the screen would remove the video this way.

Also also, instead of hiding and showing the element (like I do in this game), it would probably be best to hide it, then move it to the end of the #output element before showing it again.

I'll be back after a little testing.

I'm researching this now.


I figured out how to do this, but clearing the screen breaks it. It removes the element, and the element must always exist so the API can manipulate it. (There may be something I'm overlooking.)

If we want a YouTube video to be added with the normal output text and scroll with it, we'd probably be better off with a normal YouTube iframe (with no API controls over it).

To pull this off, the CSP still needs YouTube added, and this function should work fine:

function youTube(id='7vIi0U4rSX4',auto=true){
	killYoutube();
	var autoplay = '1';
	if (!auto) autoplay = '0';
	var s = '<iframe id="youTube" width="560" height="315"';
	s += ' src="https://www.youtube.com/embed/'+id+'?autoplay='+autoplay+'"';
	s += ' frameborder="0" allowfullscreen></iframe>';
	msg(s);
	
};

Note that clearing the screen will kill the video when its element is in the output element.


THIS JUST IN

I just test saving with this. It's a problem.

TODO: Set the preSave and afterLoad stuff on the TV to check the YouTube player status.


This topic is now closed. Topics are closed after 60 days of inactivity.

Support

Forums