1<!DOCTYPE HTML> 2<html> 3 4<!-- 5 pgn4web javascript chessboard 6 copyright (C) 2009-2015 Paolo Casaschi 7 see README file and http://pgn4web.casaschi.net 8 for credits, license and more details 9--> 10 11<head> 12 13<title>pgn4web integration with HTML5 video</title> 14 15<link rel="icon" sizes="16x16" href="pawn.ico" /> 16 17<style type="text/css"> 18 19html, 20body { 21 margin: 0px; 22 padding: 0px; 23} 24 25body { 26 color: black; 27 background: white; 28 font-family: sans-serif; 29} 30 31a { 32 color: black; 33 text-decoration: none; 34} 35 36.boardTable { 37 height: 272px; 38 width: 272px; 39 box-shadow: 0px 0px 15px #663300; 40} 41 42.boardBox { 43 width: 272px; 44 text-align:left; 45 font-weight:bold; 46 text-shadow: 1px 1px 3px #C4C4C4 47} 48 49.videoBox { 50 box-shadow: 0px 0px 16px #663300; 51} 52 53.pieceImage { 54 height: 26px; 55 width: 26px; 56} 57 58.whiteSquare, 59.blackSquare, 60.highlightWhiteSquare, 61.highlightBlackSquare { 62 width: 32px; 63 height: 32px; 64 border-style: solid; 65 border-width: 1px; 66} 67 68.whiteSquare, 69.highlightWhiteSquare { 70 border-color: #FFCC99; 71 background: #FFCC99; 72} 73 74.blackSquare, 75.highlightBlackSquare { 76 border-color: #CC9966; 77 background: #CC9966; 78} 79 80.highlightWhiteSquare, 81.highlightBlackSquare { 82 border-style: inset; 83 border-color: #CC9966; 84} 85 86</style> 87 88<script src="pgn4web.js" type="text/javascript"></script> 89 90</head> 91 92<body> 93 94<!-- paste your PGN below and make sure you dont specify an external source with SetPgnUrl() --> 95<form style="display: none;"><textarea style="display: none;" id="pgnText"> 96 97</textarea></form> 98 99<script type="text/javascript"> 100"use strict"; 101 102function gup(name) { 103 104 name = name.replace(/[\[]/,"\\\[").replace(/[\]]/,"\\\]"); 105 var regexS = "[\\?&]"+name+"=([^&#]*)"; 106 // commented below to match first occurrence (to avoid users overruling setting) 107 // regexS = regexS+"(?!.*"+regexS+")"; // matches the LAST occurrence 108 var regex = new RegExp( regexS, "i" ); 109 var results = regex.exec( window.location.href ); 110 if (results !== null) { return decodeURIComponent(results[1]); } 111 112 // allows for short version of the URL parameters, for instance sC matches squareColor 113 var compact_name = name.charAt(0); 114 for (var i=1; i<name.length; i++) { 115 if (name.charAt(i).match(/[A-Z]/)) { compact_name = compact_name + name.charAt(i).toLowerCase(); } 116 } 117 name = compact_name; 118 119 name = name.replace(/[\[]/,"\\\[").replace(/[\]]/,"\\\]"); 120 regexS = "[\\?&]"+name+"=([^&#]*)"; 121 // commented below to match first occurrence (to avoid users overruling setting) 122 // regexS = regexS+"(?!.*"+regexS+")"; // matches the LAST occurrence 123 regex = new RegExp( regexS, "i" ); 124 125 results = regex.exec( window.location.href ); 126 if (results !== null) { return decodeURIComponent(results[1]); } 127 128 return ""; 129} 130 131SetCommentsIntoMoveText(false); 132 133var pgnFile_default = ""; 134var videoUrl_default = ""; 135var videoWidth_default = "480"; 136var videoHeight_default = "270"; 137function print_default(value) { return value ? value : "null"; } 138 139var videoHelp = ((gup("help") == "true") || (gup("help") == "t")); 140 141if (videoHelp) { 142 document.write("<PRE>"); 143 document.write("pgn4web video.html URL parameters:\n"); 144 document.write("\n"); 145 document.write(" - pgnData = PGN URL to load as PGN data source (required); default " + print_default(pgnFile_default) + "\n"); 146 document.write(" - videoUrl = video URL to load as video source (required); default " + print_default(videoUrl_default) + "\n"); 147 document.write("\n"); 148 document.write(" - videoWidth = video width; default " + print_default(videoWidth_default) + "\n"); 149 document.write(" - videoHeigth = video height; default " + print_default(videoHeight_default) + "\n"); 150 document.write(" - videoAutoplay = if set true video autoplays at page load; default true\n"); 151 document.write(" - videoLoop = if set true video loops; default false\n"); 152 document.write(" - horizontalLayout = if set true the chessboard is next to the video, otherwise underneath; default true\n"); 153 document.write(" - headerDisplay = if set true game header info is shown; default true\n"); 154 document.write("\n"); 155 document.write(" - setupVideotimes = if set true, setup special mode to add video times info to the PGN data; default false\n"); 156 document.write(" - testVideotimes = if set true, detects missing/wrong video times info in the PGN data; default false\n"); 157 document.write(" - ignoreVideotimes = if set true, video times info is ignored; default false\n"); 158 document.write("\n"); 159 document.write(" - help = true\n"); 160 document.write("\n"); 161 document.write("URL parameters can be shortened: for example pgnData => pd, videoUrl => vu\n"); 162 document.write("Also supplied textual values can be shortened: true => t, false => f\n"); 163 document.write("\n"); 164 document.write("</PRE>\n<HR>\n"); 165} 166 167var horizontalLayout = ((gup("horizontalLayout") != "false") && (gup("hl") != "f")); 168var headerDisplay = ((gup("headerDisplay") != "false") && (gup("hd") != "f")); 169 170</script> 171 172<center> 173 174<table> 175 176<tr><td height="20"></td></tr> 177 178<tr><td> 179 180<div id="videoBox" class="videoBox"> 181<div id="videodiv" style="text-align:center; font-style:italic;"> 182<video id="videoPlayer" controls="controls"><br/><br/>warning: HTML5 video support required</video> 183</div> 184</div> 185 186</td> 187 188<script type="text/javascript"> 189"use strict"; 190 191if (horizontalLayout) { document.write('<td width="20"></td>'); } 192else { document.write('</tr><tr><td height="20"></td></tr><tr>'); } 193 194</script> 195 196<td align="center"> 197 198<div class="boardBox"> 199<div id="GameBlackHeader" style="margin-bottom:1em;"> 200<div id="GameBlackClock" style="float:right;"></div> 201<span id="GameBlack"></span> 202</div> 203<div id="GameBoard"><div style="width:272px; height:272px;"></div></div> 204<div id="GameWhiteHeader" style="margin-top:1em;"> 205<div id="GameWhiteClock" style="float:right;"></div> 206<span id="GameWhite"></span> 207</div> 208</div> 209 210</td></tr></table> 211 212<form id="setupBox" style="display:none;"> 213<table style="width:100%;"><tr valign="bottom"><td> 214 215<div id="videoTimeInfo" style="font-weight:bold; font-size:small; margin-top:2em;"></div> 216 217</td><td> 218 219</td><td> 220 221<div style="text-align:right; margin-top:2em;"> 222<span style="font-weight:bold; font-size:small; margin-right:0.5em; margin-left:0.5em;">time correction:</span> 223<input id="timeCorrection" type="text" value="-0.3" style="width:6em;" title="time correction in seconds to be added to the captured video time, usually negative to take into account the lag between seeing a move and clicking the capture button, default -0.3" /> 224</div> 225 226</td></tr><tr valign="bottom"><td style="width:30%"> 227 228<div style="font-weight:bold; font-size:small; margin-right:0.5em;">next move: <span id="GameNextMove"></span></div> 229 230</td><td style="width:40%"> 231 232<input id="captureButton" type="button" value="capture videotime into the PGN data" disabled="1" onclick="getVideotime();" style="width:100%;" /> 233 234</td><td style="width:30%"> 235 236<input id="togglevideoButton" type="button" value="play/pause video" disabled="1" onclick="toggleVideoPlayPause();" style="width:100%;" /> 237 238</td></tr><tr><td colspan="3"> 239 240<textarea id="setupOutput" style="height:200px; width:100%; padding: 10px; border-color: lightgray;"> 241% you can add video times information to the PGN file by pressing the capture button 242% each time a new game starts, including the first game, and each time a move is made; 243 244</textarea> 245 246</td></tr></table> 247</form> 248 249</center> 250 251<script type="text/javascript"> 252"use strict"; 253 254var theObj; 255 256if (!headerDisplay) { 257 if (theObj = document.getElementById("GameBlackHeader")) { theObj.style.display = "none"; } 258 if (theObj = document.getElementById("GameWhiteHeader")) { theObj.style.display = "none"; } 259} 260 261// accepts pgnData as alias for pgnFile for consistency with board.html 262var pgnFile; 263if ((pgnFile = gup("pgnData")) === "") { 264 if ((pgnFile = gup("pgnFile")) === "") { 265 pgnFile = pgnFile_default; 266 } 267} 268 269var videoUrl = gup("videoUrl"); 270if (videoUrl === "") { videoUrl = videoUrl_default; } 271 272var videoWidth = gup("videoWidth"); 273if (videoWidth === "") { videoWidth = videoWidth_default; } 274 275var videoHeight = gup("videoHeight"); 276if (videoHeight === "") { videoHeight = videoHeight_default; } 277 278var videoAutoplay = ((gup("videoAutoplay") != "false") && (gup("videoAutoplay") != "f")); 279 280var videoLoop = ((gup("videoLoop") == "true") || (gup("videoLoop") == "t")); 281 282var ignoreVideotimes = ((gup("ignoreVideotimes") == "true") || (gup("ignoreVideotimes") == "t")); 283 284var testVideotimes = ((gup("testVideotimes") == "true") || (gup("testVideotimes") == "t")); 285 286var setupVideotimes = ((gup("setupVideotimes") == "true") || (gup("setupVideotimes") == "t")); 287document.getElementById("setupBox").style.display = setupVideotimes ? "inline" : "none"; 288 289 290var paramError = ""; 291 292if (theObj = document.getElementById("videoBox")) { 293 if (videoWidth) { theObj.style.width = videoWidth + "px"; } 294 if (videoHeight) { theObj.style.height = videoHeight + "px"; } 295} 296 297if (videoUrl) { 298 if (theObj = document.getElementById("videoPlayer")) { 299 theObj.src = videoUrl; 300 if (videoWidth) { theObj.width = videoWidth; } 301 if (videoHeight) { theObj.height = videoHeight; } 302 if (videoAutoplay) { theObj.autoplay = "autoplay"; } 303 if (videoLoop) { theObj.loop = "loop"; } 304 } 305} else { 306 paramError += "error: videoUrl parameter required as video source\n\n"; 307} 308 309if (pgnFile) { 310 SetPgnUrl(pgnFile); 311} else { 312 paramError += "error: pgnData parameter required as PGN data source\n\n"; 313} 314 315if (paramError && (!videoHelp)) { 316 if (confirm("pgn4web video.html\n\n" + paramError + "click OK for more help")) { 317 window.location.search += (window.location.search ? "&" : "?") + "help=true"; 318 } 319} 320 321SetImagePath ("images/alpha/26"); 322SetImageType("png"); 323SetHighlightOption(true); 324SetShortcutKeysEnabled(ignoreVideotimes); 325 326if (!ignoreVideotimes) { 327 clearShortcutSquares("A", "123456"); 328 clearShortcutSquares("BCDEFGH", "1234567"); 329} 330 331var videotimeForGame = new Array(); 332function getVideotimeForGame() { 333 videotimeForGame = new Array(); 334 for (var thisGame=0; thisGame<numberOfGames; thisGame++) { 335 videotimeForGame[thisGame] = parseFloat(customPgnHeaderTag("VideoTime", null, thisGame)); 336 if (!videotimeForGame[thisGame]) { videotimeForGame[thisGame] = 0; } 337 } 338} 339 340var videotimeAtPly = new Array(); 341function getVideotimeAtPly() { 342 videotimeAtPly = new Array(); 343 for (var thisPly=StartPly; thisPly<=StartPly+PlyNumber; thisPly++) { 344 videotimeAtPly[thisPly] = parseFloat(customPgnCommentTag("vt", null, thisPly)); 345 if (!videotimeAtPly[thisPly]) { videotimeAtPly[thisPly] = 0; } 346 } 347} 348 349function customFunctionOnPgnGameLoad() { 350 if (setupVideotimes) { 351 document.getElementById("togglevideoButton").disabled = 0; 352 document.getElementById("captureButton").disabled = 0; 353 document.getElementById("captureButton").focus(); 354 } 355 getVideotimeAtPly(); 356} 357 358function runTestVidoetimes() { 359 for (var thisGame=0; thisGame<numberOfGames; thisGame++) { 360 if ((thisGame>0) && (videotimeForGame[thisGame] <= videotimeForGame[thisGame-1])) { 361 alert("warning: " + pgnFile + " missing/wrong VideoTime header tag for game " + (thisGame+1)); 362 return false; 363 } 364 Init(thisGame); 365 for (var thisPly=StartPly; thisPly<=StartPly+PlyNumber; thisPly++) { 366 if ((thisPly>StartPly) && (videotimeAtPly[thisPly] <= videotimeAtPly[thisPly-1])) { 367 alert("warning: " + pgnFile + " missing/wrong %vt comment at ply " + thisPly + " of game " + (thisGame+1)); 368 return false; 369 } 370 } 371 } 372 alert("info: " + pgnFile + " video time values looks ok"); 373 return true; 374} 375 376var syncInterval = 456; // milliseconds 377var syncTimer = null; 378function customFunctionOnPgnTextLoad() { 379 getVideotimeForGame(); 380 if (testVideotimes) { runTestVidoetimes(); } 381 if (syncTimer) { clearTimeout(syncTimer); } 382 if (!ignoreVideotimes) { syncTimer = setTimeout("syncBoard();", syncInterval); } 383} 384 385// assumes arrayValues ordered 386function findIndexInOrderedArray(target, arrayValues, arrayFirst, arrayLast, previous) { 387 if (previous < arrayFirst) { previous = arrayFirst; } 388 if (previous > arrayLast ) { previous = arrayLast ; } 389 var found = previous; 390 /*jsl:ignore*/ 391 if ((previous > arrayFirst) && (target < arrayValues[previous])) { 392 for (var index=previous; (index>arrayFirst) && (target < arrayValues[index]); index--) {} 393 found = index; 394 } else if ((previous < arrayLast) && (target > arrayValues[previous+1])) { 395 for (index=previous; (index<arrayLast) && (target > arrayValues[index+1]); index++) {} 396 found = index; 397 } 398 /*jsl:end*/ 399 return found; 400} 401 402function videoPlayerCurrentTime() { 403 var videoPlayer = document.getElementById("videoPlayer"); 404 var videoCurrentTime = 0; 405 if (videoPlayer && videoPlayer.currentTime) { videoCurrentTime = videoPlayer.currentTime; } 406 else if (videoPlayer && videoPlayer.getCurrentTime) { videoCurrentTime = videoPlayer.getCurrentTime(); } 407 document.getElementById("videoTimeInfo").innerHTML = "video time: " + (Math.round(videoCurrentTime * 1000) / 1000); 408 return videoCurrentTime; 409} 410 411function syncBoard() { 412 if (syncTimer) { clearTimeout(syncTimer); } 413 414 var videoCurrentTime = videoPlayerCurrentTime(); 415 416 if (!setupVideotimes) { 417 var foundGame = findIndexInOrderedArray(videoCurrentTime, videotimeForGame, 0, numberOfGames-1, currentGame); 418 if (foundGame !== currentGame) { Init(foundGame); } 419 420 var foundPly = findIndexInOrderedArray(videoCurrentTime, videotimeAtPly, StartPly, StartPly+PlyNumber, CurrentPly); 421 if (foundPly != CurrentPly) { GoToMove(foundPly); } 422 } 423 424 syncTimer = setTimeout("syncBoard();", syncInterval); 425} 426 427var setupOutputBox = document.getElementById("setupOutput"); 428function writeSetupOutput(text) { 429 if (setupOutputBox) { setupOutputBox.value += text; } 430 setupOutputBox.scrollTop = setupOutputBox.scrollHeight; 431} 432 433function newGameHeader(thisGame, videotime) { 434 var newHeader = pgnHeader[thisGame] ? pgnHeader[thisGame] : ""; 435 newHeader = newHeader.replace(/\[\s*VideoTime\s*"([^"]*)"\s*\]\s*/g, ""); 436 newHeader = newHeader.replace(/(^\s*|\s*$)/g, ""); 437 newHeader += "\n[VideoTime \"" + videotime + "\"]"; 438 return newHeader; 439} 440 441var startedVideotimes = false; 442var endedVideotimes = false; 443function getVideotime() { 444 if (!setupVideotimes || endedVideotimes) { return; } 445 var storedVideotime = videoPlayerCurrentTime(); 446 var timeCorrection = parseFloat(document.getElementById("timeCorrection").value); 447 if (timeCorrection) { storedVideotime += timeCorrection; } 448 storedVideotime = Math.round(storedVideotime * 10) / 10; 449 if (!startedVideotimes) { 450 writeSetupOutput("\n" + newGameHeader(currentGame, storedVideotime) + "\n\n"); 451 startedVideotimes = true; 452 return; 453 } 454 if (CurrentPly === StartPly+PlyNumber) { 455 if (currentGame+1 < numberOfGames) { 456 Init(currentGame + 1); 457 writeSetupOutput("\n" + newGameHeader(currentGame, storedVideotime) + "\n\n"); 458 } 459 } else { 460 GoToMove(CurrentPly + 1); 461 var moreText = ""; 462 moreText += Math.ceil(CurrentPly/2) + "."; 463 if (!(CurrentPly % 2)) { moreText += ".."; } 464 moreText += " " + Moves[CurrentPly-1] + " {[%vt " + storedVideotime + "]"; 465 var comment = MoveComments[CurrentPly].replace(/\s*\[%vt [\.0-9]*\]\s*/g, ""); 466 if (comment) { moreText += " " + comment.replace(/[{}]/g, ""); } 467 moreText += "}\n"; 468 writeSetupOutput(moreText); 469 } 470 if (CurrentPly === StartPly+PlyNumber) { 471 if (gameResult[currentGame]) { writeSetupOutput(gameResult[currentGame] + "\n"); } 472 if (currentGame+1 === numberOfGames) { 473 writeSetupOutput("\n\n% end of games and moves"); 474 endedVideotimes = true; 475 } 476 } 477} 478 479function customShortcutKey_Shift_0() { 480 getVideotime(); 481} 482 483function toggleVideoPlayPause() { 484 var videoPlayer = document.getElementById("videoPlayer"); 485 if (videoPlayer && videoPlayer.playVideo) { 486 if (videoPlayer.getPlayerState() == 2) { 487 videoPlayer.playVideo(); 488 } else { 489 videoPlayer.pauseVideo(); 490 } 491 } else if (videoPlayer && videoPlayer.play) { 492 if (videoPlayer.paused) { videoPlayer.play(); } 493 else { videoPlayer.pause(); } 494 } 495} 496 497</script> 498 499</body> 500 501</html> 502