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>&nbsp;
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>&nbsp;
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