GUACAMOLE-250: Add example project demonstrating in-browser recording playback.
Project: http://git-wip-us.apache.org/repos/asf/incubator-guacamole-client/repo Commit: http://git-wip-us.apache.org/repos/asf/incubator-guacamole-client/commit/7f5e1b80 Tree: http://git-wip-us.apache.org/repos/asf/incubator-guacamole-client/tree/7f5e1b80 Diff: http://git-wip-us.apache.org/repos/asf/incubator-guacamole-client/diff/7f5e1b80 Branch: refs/heads/master Commit: 7f5e1b80e301ac2f8c56f641c63fbf95c15cc0ee Parents: 79c66b0 Author: Michael Jumper <[email protected]> Authored: Thu Apr 13 00:03:05 2017 -0700 Committer: Michael Jumper <[email protected]> Committed: Thu Apr 20 22:36:15 2017 -0700 ---------------------------------------------------------------------- doc/guacamole-playback-example/.gitignore | 3 + doc/guacamole-playback-example/pom.xml | 98 ++++++++++ .../src/main/webapp/WEB-INF/web.xml | 30 +++ .../src/main/webapp/index.html | 56 ++++++ .../src/main/webapp/playback.css | 79 ++++++++ .../src/main/webapp/playback.js | 195 +++++++++++++++++++ .../src/main/webapp/recording.guac | 1 + pom.xml | 3 +- 8 files changed, 464 insertions(+), 1 deletion(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/incubator-guacamole-client/blob/7f5e1b80/doc/guacamole-playback-example/.gitignore ---------------------------------------------------------------------- diff --git a/doc/guacamole-playback-example/.gitignore b/doc/guacamole-playback-example/.gitignore new file mode 100644 index 0000000..c352b37 --- /dev/null +++ b/doc/guacamole-playback-example/.gitignore @@ -0,0 +1,3 @@ +src/main/webapp/META-INF/ +target/ +*~ http://git-wip-us.apache.org/repos/asf/incubator-guacamole-client/blob/7f5e1b80/doc/guacamole-playback-example/pom.xml ---------------------------------------------------------------------- diff --git a/doc/guacamole-playback-example/pom.xml b/doc/guacamole-playback-example/pom.xml new file mode 100644 index 0000000..66754c2 --- /dev/null +++ b/doc/guacamole-playback-example/pom.xml @@ -0,0 +1,98 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + Licensed to the Apache Software Foundation (ASF) under one + or more contributor license agreements. See the NOTICE file + distributed with this work for additional information + regarding copyright ownership. The ASF licenses this file + to you under the Apache License, Version 2.0 (the + "License"); you may not use this file except in compliance + with the License. You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, + software distributed under the License is distributed on an + "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + KIND, either express or implied. See the License for the + specific language governing permissions and limitations + under the License. +--> +<project xmlns="http://maven.apache.org/POM/4.0.0" + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 + http://maven.apache.org/maven-v4_0_0.xsd"> + + <modelVersion>4.0.0</modelVersion> + <groupId>org.apache.guacamole</groupId> + <artifactId>guacamole-playback-example</artifactId> + <packaging>war</packaging> + <version>0.9.12-incubating</version> + <name>guacamole-playback-example</name> + <url>http://guacamole.incubator.apache.org/</url> + + <properties> + <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> + </properties> + + <build> + <plugins> + + <!-- Overlay guacamole-common-js (zip) --> + <plugin> + <groupId>org.apache.maven.plugins</groupId> + <artifactId>maven-war-plugin</artifactId> + <version>2.6</version> + <configuration> + <overlays> + <overlay> + <groupId>org.apache.guacamole</groupId> + <artifactId>guacamole-common-js</artifactId> + <type>zip</type> + </overlay> + </overlays> + </configuration> + </plugin> + + <!-- Verify format using Apache RAT --> + <plugin> + <groupId>org.apache.rat</groupId> + <artifactId>apache-rat-plugin</artifactId> + <version>0.12</version> + + <configuration> + <excludes> + <exclude>src/main/webapp/recording.guac</exclude> + </excludes> + </configuration> + + <!-- Bind RAT to validate phase --> + <executions> + <execution> + <id>validate</id> + <phase>validate</phase> + <goals> + <goal>check</goal> + </goals> + </execution> + </executions> + + </plugin> + + </plugins> + + </build> + + <dependencies> + + <!-- Guacamole JavaScript library --> + <dependency> + <groupId>org.apache.guacamole</groupId> + <artifactId>guacamole-common-js</artifactId> + <version>0.9.12-incubating</version> + <type>zip</type> + <scope>runtime</scope> + </dependency> + + </dependencies> + +</project> http://git-wip-us.apache.org/repos/asf/incubator-guacamole-client/blob/7f5e1b80/doc/guacamole-playback-example/src/main/webapp/WEB-INF/web.xml ---------------------------------------------------------------------- diff --git a/doc/guacamole-playback-example/src/main/webapp/WEB-INF/web.xml b/doc/guacamole-playback-example/src/main/webapp/WEB-INF/web.xml new file mode 100644 index 0000000..9db9bb0 --- /dev/null +++ b/doc/guacamole-playback-example/src/main/webapp/WEB-INF/web.xml @@ -0,0 +1,30 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + Licensed to the Apache Software Foundation (ASF) under one + or more contributor license agreements. See the NOTICE file + distributed with this work for additional information + regarding copyright ownership. The ASF licenses this file + to you under the Apache License, Version 2.0 (the + "License"); you may not use this file except in compliance + with the License. You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, + software distributed under the License is distributed on an + "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + KIND, either express or implied. See the License for the + specific language governing permissions and limitations + under the License. +--> +<web-app version="2.5" xmlns="http://java.sun.com/xml/ns/javaee" + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:schemaLocation="http://java.sun.com/xml/ns/javaee + http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"> + + <!-- Basic config --> + <welcome-file-list> + <welcome-file>index.html</welcome-file> + </welcome-file-list> + +</web-app> http://git-wip-us.apache.org/repos/asf/incubator-guacamole-client/blob/7f5e1b80/doc/guacamole-playback-example/src/main/webapp/index.html ---------------------------------------------------------------------- diff --git a/doc/guacamole-playback-example/src/main/webapp/index.html b/doc/guacamole-playback-example/src/main/webapp/index.html new file mode 100644 index 0000000..69e79a9 --- /dev/null +++ b/doc/guacamole-playback-example/src/main/webapp/index.html @@ -0,0 +1,56 @@ +<!DOCTYPE HTML> +<!-- + Licensed to the Apache Software Foundation (ASF) under one + or more contributor license agreements. See the NOTICE file + distributed with this work for additional information + regarding copyright ownership. The ASF licenses this file + to you under the Apache License, Version 2.0 (the + "License"); you may not use this file except in compliance + with the License. You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, + software distributed under the License is distributed on an + "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + KIND, either express or implied. See the License for the + specific language governing permissions and limitations + under the License. +--> +<html> + + <head> + <title>Guacamole Recording Playback (EXAMPLE)</title> + <meta charset="utf-8"/> + <link rel="stylesheet" type="text/css" href="playback.css"> + </head> + + <body> + + <!-- Guacamole recording player --> + <div class="player"> + + <!-- Player display --> + <div id="display"></div> + + <!-- Player controls --> + <div class="controls"> + <button id="play-pause">Play</button> + <input id="position-slider" type="range"> + <span id="position">00:00</span> + <span>/</span> + <span id="duration">00:00</span> + </div> + + </div> + + <!-- Guacamole JavaScript API --> + <script type="text/javascript" + src="guacamole-common-js/all.min.js"></script> + + <!-- --> + <script type="text/javascript" src="playback.js"></script> + + </body> + +</html> http://git-wip-us.apache.org/repos/asf/incubator-guacamole-client/blob/7f5e1b80/doc/guacamole-playback-example/src/main/webapp/playback.css ---------------------------------------------------------------------- diff --git a/doc/guacamole-playback-example/src/main/webapp/playback.css b/doc/guacamole-playback-example/src/main/webapp/playback.css new file mode 100644 index 0000000..210506f --- /dev/null +++ b/doc/guacamole-playback-example/src/main/webapp/playback.css @@ -0,0 +1,79 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +.player { + width: 640px; +} + +.player .controls { + + width: 100%; + + /* IE10 */ + display: -ms-flexbox; + -ms-flex-align: center; + -ms-flex-direction: row; + + /* Ancient Mozilla */ + display: -moz-box; + -moz-box-align: center; + -moz-box-orient: horizontal; + + /* Ancient WebKit */ + display: -webkit-box; + -webkit-box-align: center; + -webkit-box-orient: horizontal; + + /* Old WebKit */ + display: -webkit-flex; + -webkit-align-items: center; + -webkit-flex-direction: row; + + /* W3C */ + display: flex; + align-items: center; + flex-direction: row; + +} + +.player .controls > * { + margin: 0.25em; +} + +.player .controls #position-slider { + -ms-flex: 1 1 auto; + -moz-box-flex: 1; + -webkit-box-flex: 1; + -webkit-flex: 1 1 auto; + flex: 1 1 auto; +} + +.player .controls #play-pause { + margin-left: 0; + min-width: 5em; +} + +.player .controls #position, +.player .controls #duration { + font-family: monospace; +} + +.player .controls #duration { + margin-right: 0; +} http://git-wip-us.apache.org/repos/asf/incubator-guacamole-client/blob/7f5e1b80/doc/guacamole-playback-example/src/main/webapp/playback.js ---------------------------------------------------------------------- diff --git a/doc/guacamole-playback-example/src/main/webapp/playback.js b/doc/guacamole-playback-example/src/main/webapp/playback.js new file mode 100644 index 0000000..170a2b5 --- /dev/null +++ b/doc/guacamole-playback-example/src/main/webapp/playback.js @@ -0,0 +1,195 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +(function initExamplePlayer() { + + /** + * The URL of the Guacamole session recording which should be played back. + * + * @constant + * @type String + */ + var RECORDING_URL = 'recording.guac'; + + /** + * The element which will contain the recording display. + * + * @type Element + */ + var display = document.getElementById('display'); + + /** + * Play/pause toggle button. + * + * @type Element + */ + var playPause = document.getElementById('play-pause'); + + /** + * Text status display indicating the current playback position within the + * recording. + * + * @type Element + */ + var position = document.getElementById('position'); + + /** + * Slider indicating the current playback position within the recording, + * and allowing the user to change the playback position. + * + * @type Element + */ + var positionSlider = document.getElementById('position-slider'); + + /** + * Text status display indicating the current length of the recording. + * + * @type Element + */ + var duration = document.getElementById('duration'); + + /** + * The tunnel which should be used to download the Guacamole session + * recording. + * + * @type Guacamole.Tunnel + */ + var tunnel = new Guacamole.StaticHTTPTunnel(RECORDING_URL); + + /** + * Guacamole.SessionRecording instance to be used to playback the session + * recording. + * + * @type Guacamole.SessionRecording + */ + var recording = new Guacamole.SessionRecording(tunnel); + + /** + * The Guacamole.Display which displays the recording during playback. + * + * @type Guacamole.Display + */ + var recordingDisplay = recording.getDisplay(); + + /** + * Converts the given number to a string, adding leading zeroes as necessary + * to reach a specific minimum length. + * + * @param {Numer} num + * The number to convert to a string. + * + * @param {Number} minLength + * The minimum length of the resulting string, in characters. + * + * @returns {String} + * A string representation of the given number, with leading zeroes + * added as necessary to reach the specified minimum length. + */ + var zeroPad = function zeroPad(num, minLength) { + + // Convert provided number to string + var str = num.toString(); + + // Add leading zeroes until string is long enough + while (str.length < minLength) + str = '0' + str; + + return str; + + }; + + /** + * Converts the given millisecond timestamp into a human-readable string in + * MM:SS format. + * + * @param {Number} millis + * An arbitrary timestamp, in milliseconds. + * + * @returns {String} + * A human-readable string representation of the given timestamp, in + * MM:SS format. + */ + var formatTime = function formatTime(millis) { + + // Calculate total number of whole seconds + var totalSeconds = Math.floor(millis / 1000); + + // Split into seconds and minutes + var seconds = totalSeconds % 60; + var minutes = Math.floor(totalSeconds / 60); + + // Format seconds and minutes as MM:SS + return zeroPad(minutes, 2) + ':' + zeroPad(seconds, 2); + + }; + + // Add playback display to DOM + display.appendChild(recordingDisplay.getElement()); + + // Begin downloading the recording + recording.connect(); + + // If playing, the play/pause button should read "Pause" + recording.onplay = function() { + playPause.textContent = 'Pause'; + }; + + // If paused, the play/pause button should read "Play" + recording.onpause = function() { + playPause.textContent = 'Play'; + }; + + // Toggle play/pause when display or button are clicked + display.onclick = playPause.onclick = function() { + if (!recording.isPlaying()) + recording.play(); + else + recording.pause(); + }; + + // Fit display within containing div + recordingDisplay.onresize = function displayResized(width, height) { + + // Do not scale if display has no width + if (!width) + return; + + // Scale display to fit width of container + recordingDisplay.scale(display.offsetWidth / width); + + }; + + // Update slider and status when playback position changes + recording.onseek = function positionChanged(millis) { + position.textContent = formatTime(millis); + positionSlider.value = millis; + }; + + // Update slider and status when duration changes + recording.onprogress = function durationChanged(millis) { + duration.textContent = formatTime(millis); + positionSlider.max = millis; + }; + + // Seek within recording if slider is moved + positionSlider.onchange = function sliderPositionChanged() { + recording.seek(positionSlider.value); + }; + +})();
