Add files via upload

This commit is contained in:
Keb Helion 2020-02-17 22:21:30 -05:00 committed by GitHub
parent 644785cf24
commit c6cfc7c1b5
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
34 changed files with 3848 additions and 0 deletions

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.2 KiB

View file

@ -0,0 +1,274 @@
<!DOCTYPE html><html>
<head>
<title>Domain Navigator</title>
<script src="jquery.min.js"></script>
<script type="text/javascript">
function getParameter(theParameter) {
var params = window.location.search.substr(1).split('&');
for (var i = 0; i < params.length; i++) {
var p=params[i].split('=');
if (p[0] == theParameter) {
return decodeURIComponent(p[1]);
}
}
return false;
}
function main(){
$(".tpbutton").click(function(){
var clickEventz = {
"type" : "give_tp_coor",
"x" : document.coord.x.value,
"y" : document.coord.y.value,
"z" : document.coord.z.value
};
EventBridge.emitWebEvent(JSON.stringify(clickEventz));
})
$(".looknorth").click(function(){
var clickEventz = {
"type" : "look_to_north"
};
EventBridge.emitWebEvent(JSON.stringify(clickEventz));
})
$(".looksouth").click(function(){
var clickEventz = {
"type" : "look_to_south"
};
EventBridge.emitWebEvent(JSON.stringify(clickEventz));
})
$(".lookwest").click(function(){
var clickEventz = {
"type" : "look_to_west"
};
EventBridge.emitWebEvent(JSON.stringify(clickEventz));
})
$(".lookeast").click(function(){
var clickEventz = {
"type" : "look_to_east"
};
EventBridge.emitWebEvent(JSON.stringify(clickEventz));
})
}
$(document).ready(main);
function FindPosition(oElement)
{
if(typeof( oElement.offsetParent ) != "undefined")
{
for(var posX = 0, posY = 0; oElement; oElement = oElement.offsetParent)
{
posX += oElement.offsetLeft;
posY += oElement.offsetTop;
}
return [ posX, posY ];
}
else
{
return [ oElement.x, oElement.y ];
}
}
function GetXZCoordinates(e)
{
var PosX = 0;
var PosY = 0;
var ImgPos;
ImgPos = FindPosition(cnvs1);
if (!e) var e = window.event;
if (e.pageX || e.pageY)
{
PosX = e.pageX;
PosY = e.pageY;
}
else if (e.clientX || e.clientY)
{
PosX = e.clientX + document.body.scrollLeft
+ document.documentElement.scrollLeft;
PosY = e.clientY + document.body.scrollTop
+ document.documentElement.scrollTop;
}
PosX = ((PosX - ImgPos[0])-200)*80;
PosY = ((PosY - ImgPos[1])-200)*80;
document.coord.x.value = PosX;
document.coord.z.value = PosY;
DrawPlanPos(PosX,PosY);
}
function GetYCoordinates(e2)
{
var PosX = 0;
var PosY = 0;
var ImgPos;
ImgPos = FindPosition(cnvs2);
if (!e2) var e2 = window.event;
if (e2.pageX || e2.pageY)
{
PosX = e2.pageX;
PosY = e2.pageY;
}
else if (e2.clientX || e2.clientY)
{
PosX = e2.clientX + document.body.scrollLeft
+ document.documentElement.scrollLeft;
PosY = e2.clientY + document.body.scrollTop
+ document.documentElement.scrollTop;
}
PosY = (200-(PosY - ImgPos[1]))*80;
document.coord.y.value = PosY;
DrawAltiPos(PosY);
}
</script>
<style>
body{
background-color: #43484f;
}
font.maintitle{
font-family: Arial, Helvetica, sans-serif;
font-size: 40px;
color: #FFFFFF;
font-weight: 700;
}
td{
font-family: Arial, Helvetica, sans-serif;
font-size: 18px;
color: #FFFFFF;
font-weight: 700;
}
input.x{
font-family: Arial, Helvetica, sans-serif;
font-size: 14px;
color: #a30000;
font-weight: 700;
}
input.y{
font-family: Arial, Helvetica, sans-serif;
font-size: 14px;
color: #007505;
font-weight: 700;
}
input.z{
font-family: Arial, Helvetica, sans-serif;
font-size: 14px;
color: #000cba;
font-weight: 700;
}
</style>
</head>
<body>
<div align = 'center'><font class='maintitle'>Domain Navigator</font><br><br>
<table><tr>
<td><canvas id='planCanvas' width='400px' height='400px' style="background: url('plan.png')"></canvas></td>
<td style='width:10px'></td>
<td><canvas id='altiCanvas' width='20px' height='400px' style="background: url('alti.png')"></canvas></td>
</tr>
</table>
<script type="text/javascript">
var myImg = document.getElementById("plan");
var myImg2 = document.getElementById("alti");
var cnvs1 = document.getElementById("planCanvas");
var cnvs2 = document.getElementById("altiCanvas");
cnvs1.onmousedown = GetXZCoordinates;
cnvs2.onmousedown = GetYCoordinates;
var backgroundImage1 = new Image();
backgroundImage1.src = 'plan.png';
var backgroundImage2 = new Image();
backgroundImage2.src = 'alti.png';
function DrawPlanPos(x,z){
ctx = cnvs1.getContext("2d");
ctx.drawImage(backgroundImage1, 0, 0);
ctx.beginPath();
ctx.arc(200+(x/80), 200+(z/80), 3, 0, 2 * Math.PI, false);
ctx.lineWidth = 2;
ctx.strokeStyle = '#6600ff'; //1E62D0
ctx.stroke();
}
function DrawAltiPos(y){
ctx = cnvs2.getContext("2d");
ctx.drawImage(backgroundImage2, 0, 0);
ctx.beginPath();
ctx.moveTo(0, 200-(y/80));
ctx.lineTo(20, 200-(y/80));
ctx.lineWidth = 2;
ctx.strokeStyle = '#6600ff';
ctx.stroke();
}
</script>
<form name = "coord" id = "coord">
<table style='width:430px;'><tr>
<td>X: <input name = "x" id = "x" class="x" type = "text" value = '0' size='6'></td>
<td>Y: <input name = "y" id = "y" class="y" type = "text" value = '0' size='6'></td>
<td>Z: <input name = "z" id = "z" class="z" type = "text" value = '0' size='6'></td>
</tr></table>
</form>
<table style='width:90%;'><tr><td>
<button class = 'tp tpbutton' value='' style='background:#1E62D0; color:#FFFFFF; font-family:Arial, Helvetica, sans-serif; font-size: 24px; font-weight:700; padding:6px; width:80%;'>Teleport</button>
<br><br>
</td><td style='width:95px;'>
<table>
<tr>
<td></td>
<td><img class = 'tp looknorth' src='north.png'></td>
<td></td>
</tr>
<tr>
<td><img class = 'tp lookwest' src='west.png'></td>
<td><img src='eye.png'></td>
<td><img class = 'tp lookeast' src='east.png'></td>
</tr>
<tr>
<td></td>
<td><img class = 'tp looksouth' src='south.png'></td>
<td></td>
</tr>
</table>
</td></tr></table></div>
<script>
document.coord.x.value = getParameter('x');
document.coord.y.value = getParameter('y');
document.coord.z.value = getParameter('z');
DrawPlanPos(document.coord.x.value,document.coord.z.value);
DrawAltiPos(document.coord.y.value);
</script>
</body>
</html>

View file

@ -0,0 +1,117 @@
// dom_nav.js
//
// Created by Alezia Kurdis, July 2018
//
// This tool is to help teleporting yourself rapidly where you need in a domain in a couple of clicks,
// without having to enter numbers in a path. Ideal for those who are working on large landscapes.
// Precision: 80 meters.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
(function() {
var MainPath = Script.resolvePath('').split("dom_nav.js")[0];
var APP_NAME = "DOM NAV";
var APP_URL = MainPath + "dom_nav.html";
var APP_ICON_INACTIVE = MainPath + "dom_nav_icon_i.png";
var APP_ICON_ACTIVE = MainPath + "dom_nav_icon_a.png";
var statusApp = false;
var tablet = Tablet.getTablet("com.highfidelity.interface.tablet.system");
tablet.screenChanged.connect(onScreenChanged);
var button = tablet.addButton({
text: APP_NAME,
icon: APP_ICON_INACTIVE,
activeIcon: APP_ICON_ACTIVE
});
function clicked(){
if (statusApp == true){
tablet.webEventReceived.disconnect(onWebEventReceivedz);
tablet.gotoHomeScreen();
statusApp = false;
}else{
var AvatarPosition = MyAvatar.position;
tablet.gotoWebScreen(APP_URL + "?x=" + Math.round(AvatarPosition.x) + "&y=" + Math.round(AvatarPosition.y) + "&z=" + Math.round(AvatarPosition.z));
tablet.webEventReceived.connect(onWebEventReceivedz);
statusApp = true;
}
button.editProperties({
isActive: statusApp
});
}
button.clicked.connect(clicked);
function onWebEventReceivedz(eventz){
if(typeof eventz === "string"){
eventzget = JSON.parse(eventz);
if(eventzget.type === "give_tp_coor"){
var myVec = {
x: parseFloat(eventzget.x),
y: parseFloat(eventzget.y),
z: parseFloat(eventzget.z)
};
MyAvatar.goToLocation(myVec, false);
}
if(eventzget.type === "look_to_north"){
//print("Look at North!")
MyAvatar.goToLocation(MyAvatar.position, true,{ x: 0, y: 0, z: 0, w:1 },false);
}
if(eventzget.type === "look_to_south"){
//print("Look at South!")
MyAvatar.goToLocation(MyAvatar.position, true,{ x: 0, y: 1, z: 0, w:0 },false);
}
if(eventzget.type === "look_to_west"){
//print("Look at West!")
MyAvatar.goToLocation(MyAvatar.position, true,{ x: 0, y: 0.7071068, z: 0, w:0.7071068 },false);
}
if(eventzget.type === "look_to_east"){
//print("Look at East!")
MyAvatar.goToLocation(MyAvatar.position, true,{ x: 0, y: 0.7071068, z: 0, w:-0.7071068 },false);
}
}
}
tablet.webEventReceived.connect(onWebEventReceivedz);
function onScreenChanged(type, url) {
if (type == "Web" && url.indexOf(APP_URL) != -1){
statusApp = true;
}else{
statusApp = false;
}
button.editProperties({
isActive: statusApp
});
}
function cleanup() {
tablet.webEventReceived.disconnect(onWebEventReceivedz);
tablet.screenChanged.disconnect(onScreenChanged);
tablet.removeButton(button);
}
Script.scriptEnding.connect(cleanup);
}());

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 896 B

File diff suppressed because one or more lines are too long

View file

@ -0,0 +1,8 @@
Domain Navigator
This tool is to help teleporting yourself rapidly where you need in a domain in a couple of clicks, without having to enter numbers in a path. Ideal for those who are working on large landscapes. (Precision: 80 meters.)
domain-navigator
dom_nav.js
dom_nav_icon_i.png
DOM NAV

Binary file not shown.

After

Width:  |  Height:  |  Size: 2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.7 KiB

View file

@ -0,0 +1,109 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Generator: Adobe Illustrator 19.2.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
version="1.1"
id="Layer_1"
x="0px"
y="0px"
viewBox="0 0 50 50"
style="enable-background:new 0 0 50 50;"
xml:space="preserve"
sodipodi:docname="avatar-record-a.svg"
inkscape:version="0.92.1 r15371"><metadata
id="metadata36"><rdf:RDF><cc:Work
rdf:about=""><dc:format>image/svg+xml</dc:format><dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" /><dc:title></dc:title></cc:Work></rdf:RDF></metadata><defs
id="defs34" /><sodipodi:namedview
pagecolor="#ff0000"
bordercolor="#666666"
borderopacity="1"
objecttolerance="10"
gridtolerance="10"
guidetolerance="10"
inkscape:pageopacity="0"
inkscape:pageshadow="2"
inkscape:window-width="1829"
inkscape:window-height="1057"
id="namedview32"
showgrid="false"
inkscape:zoom="4.72"
inkscape:cx="-9.4279661"
inkscape:cy="25"
inkscape:window-x="83"
inkscape:window-y="-8"
inkscape:window-maximized="1"
inkscape:current-layer="Layer_1" /><style
type="text/css"
id="style2">
.st0{fill:#FFFFFF;}
</style><g
id="Layer_2" /><g
id="g879"><path
class="st0"
d="m 23.2,20.5 c -1,0.8 -1.8,1.4 -2.7,2.1 -0.2,0.1 -0.2,0.4 -0.2,0.7 -0.3,1.7 -0.6,3.4 -0.9,5.1 -0.1,0.8 -0.6,1.2 -1.3,1.1 -0.7,-0.1 -1.2,-0.7 -1.1,-1.4 0.3,-2.2 0.6,-4.4 1,-6.6 0.1,-0.3 0.3,-0.7 0.6,-0.9 1.4,-1.3 2.8,-2.5 4.2,-3.7 0.7,-0.6 1.5,-1 2.4,-0.9 0.3,0 0.7,0 1,0 1,-0.1 1.7,0.4 2.1,1.3 0.7,1.4 1.4,2.8 1.9,4.3 0.5,1.3 1.2,2.1 2.4,2.6 1,0.4 2,1 3,1.5 0.2,0.1 0.5,0.3 0.7,0.5 0.4,0.4 0.5,1 0.3,1.4 C 36.4,28 36,28.1 35.5,28 35.1,27.9 34.7,27.8 34.3,27.6 33,27 31.8,26.4 30.6,25.8 29.8,25.5 29.2,25 28.8,24.2 c -0.2,-0.3 -0.4,-0.6 -0.7,-1 -0.1,0.3 -0.1,0.5 -0.2,0.7 -0.3,1.2 -0.5,2.4 -0.8,3.6 -0.1,0.4 0,0.7 0.2,1 2.2,3.7 4.4,7.4 6.6,11.1 0.3,0.4 0.4,1 0.5,1.5 0.1,0.7 -0.1,1.3 -0.7,1.6 C 33,43.1 32.3,43.1 31.8,42.6 31.4,42.2 31,41.8 30.7,41.3 28.2,37.4 25.7,33.4 23.2,29.5 22.8,28.8 22.4,28 22.1,27.3 22,26.9 22,26.4 22.1,26 c 0.4,-1.8 0.7,-3.6 1.1,-5.5 z"
id="path5"
inkscape:connector-curvature="0"
style="fill:#000000;fill-opacity:1" /><path
class="st0"
d="M 23.2,33.9 C 23.1,33.8 23,33.7 23,33.6 c 0,0 0,0 0,0 -0.2,-0.2 -0.3,-0.5 -0.5,-0.7 -0.3,-0.4 -0.6,-0.8 -0.9,-1.1 -0.3,1 -0.5,2 -0.8,3 -0.1,0.3 -0.3,0.7 -0.4,1 -1,1.5 -2,3.1 -3,4.6 -0.2,0.4 -0.4,0.8 -0.6,1.3 -0.2,0.9 0.7,1.9 1.6,1.5 0.5,-0.2 1,-0.7 1.3,-1.1 0.9,-1.1 1.6,-2.3 2.5,-3.3 0.8,-1 1.4,-2.2 1.8,-3.4 -0.2,-0.7 -0.5,-1.1 -0.8,-1.5 z"
id="path7"
inkscape:connector-curvature="0"
style="fill:#000000;fill-opacity:1" /><path
class="st0"
d="M 29,11.6 C 29,12.9 27.9,14 26.6,14 H 26.4 C 25.1,14 24,12.9 24,11.6 V 10.4 C 24,9.1 25.1,8 26.4,8 h 0.2 c 1.3,0 2.4,1.1 2.4,2.4 z"
id="path9"
inkscape:connector-curvature="0"
style="fill:#000000;fill-opacity:1" /><path
class="st0"
d="m 43.4,24.1 c -0.5,0.3 -0.9,0.5 -1.4,0.8 v 6.3 h 2.3 v -7.6 c -0.3,0.2 -0.6,0.3 -0.9,0.5 z"
id="path11"
inkscape:connector-curvature="0"
style="fill:#000000;fill-opacity:1" /><path
class="st0"
d="M 42,38.6 V 39 c 0,1.2 -1,2.1 -2.1,2.1 h -0.8 v 2.3 h 0.8 c 2.5,0 4.5,-2 4.5,-4.5 V 38.5 H 42 Z"
id="path13"
inkscape:connector-curvature="0"
style="fill:#000000;fill-opacity:1" /><path
class="st0"
d="m 9.7,12.2 v -0.4 c 0,-1.2 1,-2.1 2.1,-2.1 h 2 V 7.3 h -2 c -2.5,0 -4.5,2 -4.5,4.5 v 0.4 z"
id="path15"
inkscape:connector-curvature="0"
style="fill:#000000;fill-opacity:1" /><rect
x="7.4000001"
y="18.299999"
class="st0"
width="2.3"
height="12.9"
id="rect17"
style="fill:#000000;fill-opacity:1" /><path
class="st0"
d="M 9.7,38.9 V 38.5 H 7.4 v 0.4 c 0,2.5 2,4.5 4.5,4.5 h 2 v -2.3 h -2 c -1.2,0 -2.2,-1 -2.2,-2.2 z"
id="path19"
inkscape:connector-curvature="0"
style="fill:#000000;fill-opacity:1" /><g
style="fill:#000000;fill-opacity:1"
id="g25"><circle
class="st0"
cx="38.599998"
cy="13.3"
r="2.2"
id="circle21"
style="fill:#000000;fill-opacity:1" /><path
class="st0"
d="m 38.6,15.5 c -1.2,0 -2.2,-1 -2.2,-2.2 0,-1.2 1,-2.2 2.2,-2.2 1.2,0 2.2,1 2.2,2.2 0,1.2 -1,2.2 -2.2,2.2 z m 0,-4.3 c -1.1,0 -2.1,0.9 -2.1,2.1 0,1.2 0.9,2.1 2.1,2.1 1.1,0 2.1,-0.9 2.1,-2.1 0,-1.2 -1,-2.1 -2.1,-2.1 z"
id="path23"
inkscape:connector-curvature="0"
style="fill:#000000;fill-opacity:1" /></g><path
class="st0"
d="m 38.6,19.7 c -3.6,0 -6.4,-2.9 -6.4,-6.4 0,-3.5 2.9,-6.4 6.4,-6.4 3.6,0 6.4,2.9 6.4,6.4 0,3.5 -2.9,6.4 -6.4,6.4 z m 0,-10.6 c -2.3,0 -4.2,1.9 -4.2,4.2 0,2.3 1.9,4.2 4.2,4.2 2.3,0 4.2,-1.9 4.2,-4.2 0,-2.3 -1.9,-4.2 -4.2,-4.2 z"
id="path27"
inkscape:connector-curvature="0"
style="fill:#000000;fill-opacity:1" /></g></svg>

After

Width:  |  Height:  |  Size: 5.4 KiB

View file

@ -0,0 +1,36 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 19.2.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
viewBox="0 0 50 50" style="enable-background:new 0 0 50 50;" xml:space="preserve">
<style type="text/css">
.st0{fill:#FFFFFF;}
</style>
<g id="Layer_2">
</g>
<g>
<path class="st0" d="M23.2,20.5c-1,0.8-1.8,1.4-2.7,2.1c-0.2,0.1-0.2,0.4-0.2,0.7c-0.3,1.7-0.6,3.4-0.9,5.1
c-0.1,0.8-0.6,1.2-1.3,1.1c-0.7-0.1-1.2-0.7-1.1-1.4c0.3-2.2,0.6-4.4,1-6.6c0.1-0.3,0.3-0.7,0.6-0.9c1.4-1.3,2.8-2.5,4.2-3.7
c0.7-0.6,1.5-1,2.4-0.9c0.3,0,0.7,0,1,0c1-0.1,1.7,0.4,2.1,1.3c0.7,1.4,1.4,2.8,1.9,4.3c0.5,1.3,1.2,2.1,2.4,2.6c1,0.4,2,1,3,1.5
c0.2,0.1,0.5,0.3,0.7,0.5c0.4,0.4,0.5,1,0.3,1.4C36.4,28,36,28.1,35.5,28c-0.4-0.1-0.8-0.2-1.2-0.4c-1.3-0.6-2.5-1.2-3.7-1.8
c-0.8-0.3-1.4-0.8-1.8-1.6c-0.2-0.3-0.4-0.6-0.7-1c-0.1,0.3-0.1,0.5-0.2,0.7c-0.3,1.2-0.5,2.4-0.8,3.6c-0.1,0.4,0,0.7,0.2,1
c2.2,3.7,4.4,7.4,6.6,11.1c0.3,0.4,0.4,1,0.5,1.5c0.1,0.7-0.1,1.3-0.7,1.6c-0.7,0.4-1.4,0.4-1.9-0.1c-0.4-0.4-0.8-0.8-1.1-1.3
c-2.5-3.9-5-7.9-7.5-11.8c-0.4-0.7-0.8-1.5-1.1-2.2c-0.1-0.4-0.1-0.9,0-1.3C22.5,24.2,22.8,22.4,23.2,20.5z"/>
<path class="st0" d="M23.2,33.9c-0.1-0.1-0.2-0.2-0.2-0.3c0,0,0,0,0,0c-0.2-0.2-0.3-0.5-0.5-0.7c-0.3-0.4-0.6-0.8-0.9-1.1
c-0.3,1-0.5,2-0.8,3c-0.1,0.3-0.3,0.7-0.4,1c-1,1.5-2,3.1-3,4.6c-0.2,0.4-0.4,0.8-0.6,1.3c-0.2,0.9,0.7,1.9,1.6,1.5
c0.5-0.2,1-0.7,1.3-1.1c0.9-1.1,1.6-2.3,2.5-3.3c0.8-1,1.4-2.2,1.8-3.4C23.8,34.7,23.5,34.3,23.2,33.9z"/>
<path class="st0" d="M29,11.6c0,1.3-1.1,2.4-2.4,2.4h-0.2c-1.3,0-2.4-1.1-2.4-2.4v-1.2C24,9.1,25.1,8,26.4,8h0.2
c1.3,0,2.4,1.1,2.4,2.4V11.6z"/>
<path class="st0" d="M43.4,24.1c-0.5,0.3-0.9,0.5-1.4,0.8v6.3h2.3v-7.6C44,23.8,43.7,23.9,43.4,24.1z"/>
<path class="st0" d="M42,38.6v0.4c0,1.2-1,2.1-2.1,2.1h-0.8v2.3h0.8c2.5,0,4.5-2,4.5-4.5v-0.4H42z"/>
<path class="st0" d="M9.7,12.2v-0.4c0-1.2,1-2.1,2.1-2.1h2V7.3h-2c-2.5,0-4.5,2-4.5,4.5v0.4H9.7z"/>
<rect x="7.4" y="18.3" class="st0" width="2.3" height="12.9"/>
<path class="st0" d="M9.7,38.9v-0.4H7.4v0.4c0,2.5,2,4.5,4.5,4.5h2v-2.3h-2C10.7,41.1,9.7,40.1,9.7,38.9z"/>
<g>
<circle class="st0" cx="38.6" cy="13.3" r="2.2"/>
<path class="st0" d="M38.6,15.5c-1.2,0-2.2-1-2.2-2.2s1-2.2,2.2-2.2c1.2,0,2.2,1,2.2,2.2S39.8,15.5,38.6,15.5z M38.6,11.2
c-1.1,0-2.1,0.9-2.1,2.1s0.9,2.1,2.1,2.1c1.1,0,2.1-0.9,2.1-2.1S39.7,11.2,38.6,11.2z"/>
</g>
<path class="st0" d="M38.6,19.7c-3.6,0-6.4-2.9-6.4-6.4s2.9-6.4,6.4-6.4c3.6,0,6.4,2.9,6.4,6.4S42.1,19.7,38.6,19.7z M38.6,9.1
c-2.3,0-4.2,1.9-4.2,4.2s1.9,4.2,4.2,4.2c2.3,0,4.2-1.9,4.2-4.2S40.9,9.1,38.6,9.1z"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 2.6 KiB

Binary file not shown.

Binary file not shown.

Binary file not shown.

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,218 @@
/*
// record.css
//
// Created by David Rowe on 5 Apr 2017.
// Copyright 2017 High Fidelity, Inc.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
*/
body {
padding: 0;
overflow: hidden;
}
.title {
padding-left: 21px;
}
.title label {
font-size: 18px;
position: relative;
top: 12px;
}
#recordings {
height: 100%;
position: relative;
box-sizing: border-box;
padding: 51px 0 185px 0;
margin: 0 21px 0 21px;
}
#recordings #table-container {
height: 100%;
width: 100%;
overflow-x: hidden;
overflow-y: auto;
box-sizing: border-box;
border-left: 2px solid #575757;
border-right: 2px solid #575757;
background-color: #2e2e2e;
}
#recordings table {
border: none;
}
#recordings thead {
position: absolute;
top: 21px;
left: 0;
width: 100%;
box-sizing: border-box;
border: 2px solid #575757;
border-top-left-radius: 7px;
border-top-right-radius: 7px;
border-bottom: 1px solid #575757;
position: absolute;
word-wrap: nowrap;
white-space: nowrap;
overflow: hidden;
}
#recordings table col#unload-column {
width: 100px;
}
#recordings thead th:last-child {
width: 100px;
}
#recordings table td {
text-overflow: ellipsis;
}
#recordings table td:nth-child(2) {
text-align: center;
}
#recordings tbody tr.filler td {
height: auto;
border-top: 1px solid #1c1c1c;
}
#recordings-list input {
height: 22px;
width: 22px;
min-width: 22px;
font-size: 16px;
padding: 0 1px 0 0;
}
#recordings tfoot {
position: absolute;
bottom: 159px;
left: 0;
width: 100%;
box-sizing: border-box;
border: 2px solid #575757;
border-bottom-left-radius: 7px;
border-bottom-right-radius: 7px;
border-top: 1px solid #575757;
}
#recordings tfoot tr, #recordings tfoot td {
background: none;
}
#spinner {
text-align: center;
margin-top: 25%;
position: relative;
}
#spinner span {
display: block;
position: relative;
top: -101px;
color: #e2334d;
font-size: 60px;
font-weight: bold;
}
#recordings tfoot tr {
height: 24px;
}
#instructions td {
white-space: normal;
}
#instructions h1 {
font-size: 16px;
margin-top: 28px;
}
#instructions h1 + p {
margin-top: 14px;
}
#instructions p, #instructions ul {
margin-top: 21px;
font-size: 14px;
}
#instructions p {
font-family: Raleway-Bold;
}
#instructions ul {
font-family: Raleway-SemiBold;
margin-left: 21px;
font-weight: normal;
}
#instructions li {
margin-top: 7px;
}
#instructions ul input {
margin-left: 1px;
margin-top: 6px;
font-size: 14px;
padding: 0 7px;
}
#show-info-button {
font-family: HiFi-Glyphs;
font-size: 32px;
height: 16px;
line-height: 16px;
display: inline-block;
position: absolute;
top: 15px;
right: 5px;
margin-top: -11px;
margin-left: 7px;
}
#show-info-button:hover {
color: #00b4ef;
}
#record-controls {
position: absolute;
bottom: 7px;
width: 100%;
}
#record-controls #load-container {
position: absolute;
left: 21px;
}
#record-controls #record-container {
text-align: center;
}
#record-controls #checkbox-container {
margin-top: 31px;
}
#record-controls div.property {
padding-left: 21px;
}
.hidden {
display: none;
}

Binary file not shown.

View file

@ -0,0 +1,94 @@
Digitized data copyright (c) 2012-2015, The Mozilla Foundation and Telefonica S.A.
with Reserved Font Name < Fira >,
This Font Software is licensed under the SIL Open Font License, Version 1.1.
This license is copied below, and is also available with a FAQ at:
http://scripts.sil.org/OFL
-----------------------------------------------------------
SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007
-----------------------------------------------------------
PREAMBLE
The goals of the Open Font License (OFL) are to stimulate worldwide
development of collaborative font projects, to support the font creation
efforts of academic and linguistic communities, and to provide a free and
open framework in which fonts may be shared and improved in partnership
with others.
The OFL allows the licensed fonts to be used, studied, modified and
redistributed freely as long as they are not sold by themselves. The
fonts, including any derivative works, can be bundled, embedded,
redistributed and/or sold with any software provided that any reserved
names are not used by derivative works. The fonts and derivatives,
however, cannot be released under any other type of license. The
requirement for fonts to remain under this license does not apply
to any document created using the fonts or their derivatives.
DEFINITIONS
"Font Software" refers to the set of files released by the Copyright
Holder(s) under this license and clearly marked as such. This may
include source files, build scripts and documentation.
"Reserved Font Name" refers to any names specified as such after the
copyright statement(s).
"Original Version" refers to the collection of Font Software components as
distributed by the Copyright Holder(s).
"Modified Version" refers to any derivative made by adding to, deleting,
or substituting -- in part or in whole -- any of the components of the
Original Version, by changing formats or by porting the Font Software to a
new environment.
"Author" refers to any designer, engineer, programmer, technical
writer or other person who contributed to the Font Software.
PERMISSION & CONDITIONS
Permission is hereby granted, free of charge, to any person obtaining
a copy of the Font Software, to use, study, copy, merge, embed, modify,
redistribute, and sell modified and unmodified copies of the Font
Software, subject to the following conditions:
1) Neither the Font Software nor any of its individual components,
in Original or Modified Versions, may be sold by itself.
2) Original or Modified Versions of the Font Software may be bundled,
redistributed and/or sold with any software, provided that each copy
contains the above copyright notice and this license. These can be
included either as stand-alone text files, human-readable headers or
in the appropriate machine-readable metadata fields within text or
binary files as long as those fields can be easily viewed by the user.
3) No Modified Version of the Font Software may use the Reserved Font
Name(s) unless explicit written permission is granted by the corresponding
Copyright Holder. This restriction only applies to the primary font name as
presented to the users.
4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font
Software shall not be used to promote, endorse or advertise any
Modified Version, except to acknowledge the contribution(s) of the
Copyright Holder(s) and the Author(s) or with their explicit written
permission.
5) The Font Software, modified or unmodified, in part or in whole,
must be distributed entirely under this license, and must not be
distributed under any other license. The requirement for fonts to
remain under this license does not apply to any document created
using the Font Software.
TERMINATION
This license becomes null and void if any of the above conditions are
not met.
DISCLAIMER
THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE
COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM
OTHER DEALINGS IN THE FONT SOFTWARE.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View file

@ -0,0 +1,94 @@
Copyright (c) 2010, Matt McInerney (matt@pixelspread.com),
Copyright (c) 2011, Pablo Impallari (www.impallari.com|impallari@gmail.com),
Copyright (c) 2011, Rodrigo Fuenzalida (www.rfuenzalida.com|hello@rfuenzalida.com), with Reserved Font Name Raleway
This Font Software is licensed under the SIL Open Font License, Version 1.1.
This license is copied below, and is also available with a FAQ at:
http://scripts.sil.org/OFL
-----------------------------------------------------------
SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007
-----------------------------------------------------------
PREAMBLE
The goals of the Open Font License (OFL) are to stimulate worldwide
development of collaborative font projects, to support the font creation
efforts of academic and linguistic communities, and to provide a free and
open framework in which fonts may be shared and improved in partnership
with others.
The OFL allows the licensed fonts to be used, studied, modified and
redistributed freely as long as they are not sold by themselves. The
fonts, including any derivative works, can be bundled, embedded,
redistributed and/or sold with any software provided that any reserved
names are not used by derivative works. The fonts and derivatives,
however, cannot be released under any other type of license. The
requirement for fonts to remain under this license does not apply
to any document created using the fonts or their derivatives.
DEFINITIONS
"Font Software" refers to the set of files released by the Copyright
Holder(s) under this license and clearly marked as such. This may
include source files, build scripts and documentation.
"Reserved Font Name" refers to any names specified as such after the
copyright statement(s).
"Original Version" refers to the collection of Font Software components as
distributed by the Copyright Holder(s).
"Modified Version" refers to any derivative made by adding to, deleting,
or substituting -- in part or in whole -- any of the components of the
Original Version, by changing formats or by porting the Font Software to a
new environment.
"Author" refers to any designer, engineer, programmer, technical
writer or other person who contributed to the Font Software.
PERMISSION & CONDITIONS
Permission is hereby granted, free of charge, to any person obtaining
a copy of the Font Software, to use, study, copy, merge, embed, modify,
redistribute, and sell modified and unmodified copies of the Font
Software, subject to the following conditions:
1) Neither the Font Software nor any of its individual components,
in Original or Modified Versions, may be sold by itself.
2) Original or Modified Versions of the Font Software may be bundled,
redistributed and/or sold with any software, provided that each copy
contains the above copyright notice and this license. These can be
included either as stand-alone text files, human-readable headers or
in the appropriate machine-readable metadata fields within text or
binary files as long as those fields can be easily viewed by the user.
3) No Modified Version of the Font Software may use the Reserved Font
Name(s) unless explicit written permission is granted by the corresponding
Copyright Holder. This restriction only applies to the primary font name as
presented to the users.
4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font
Software shall not be used to promote, endorse or advertise any
Modified Version, except to acknowledge the contribution(s) of the
Copyright Holder(s) and the Author(s) or with their explicit written
permission.
5) The Font Software, modified or unmodified, in part or in whole,
must be distributed entirely under this license, and must not be
distributed under any other license. The requirement for fonts to
remain under this license does not apply to any document created
using the Font Software.
TERMINATION
This license becomes null and void if any of the above conditions are
not met.
DISCLAIMER
THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE
COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM
OTHER DEALINGS IN THE FONT SOFTWARE.

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 38 KiB

View file

@ -0,0 +1,293 @@
"use strict";
//
// record.js
//
// Created by David Rowe on 5 Apr 2017.
// Copyright 2017 High Fidelity, Inc.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
var isUsingToolbar = false,
isDisplayingInstructions = false,
isRecording = false,
numberOfPlayers = 0,
recordingsBeingPlayed = [],
elRecordings,
elRecordingsTable,
elRecordingsList,
elInstructions,
elPlayersUnused,
elHideInfoButton,
elShowInfoButton,
elLoadButton,
elSpinner,
elCountdownNumber,
elRecordButton,
elFinishOnOpen,
elFinishOnOpenLabel,
EVENT_BRIDGE_TYPE = "record",
BODY_LOADED_ACTION = "bodyLoaded",
USING_TOOLBAR_ACTION = "usingToolbar",
RECORDINGS_BEING_PLAYED_ACTION = "recordingsBeingPlayed",
NUMBER_OF_PLAYERS_ACTION = "numberOfPlayers",
STOP_PLAYING_RECORDING_ACTION = "stopPlayingRecording",
LOAD_RECORDING_ACTION = "loadRecording",
START_RECORDING_ACTION = "startRecording",
SET_COUNTDOWN_NUMBER_ACTION = "setCountdownNumber",
STOP_RECORDING_ACTION = "stopRecording",
FINISH_ON_OPEN_ACTION = "finishOnOpen";
function stopPlayingRecording(event) {
var playerID = event.target.getAttribute("playerID");
EventBridge.emitWebEvent(JSON.stringify({
type: EVENT_BRIDGE_TYPE,
action: STOP_PLAYING_RECORDING_ACTION,
value: playerID
}));
}
function updatePlayersUnused() {
elPlayersUnused.innerHTML = numberOfPlayers - recordingsBeingPlayed.length;
}
function updateRecordings() {
var tbody,
tr,
td,
input,
ths,
tds,
length,
i,
HIFI_GLYPH_CLOSE = "w";
tbody = document.createElement("tbody");
tbody.id = "recordings-list";
// <tr><td>Filename</td><td><input type="button" class="glyph red" value="w" playerID=id /></td></tr>
for (i = 0, length = recordingsBeingPlayed.length; i < length; i += 1) {
tr = document.createElement("tr");
td = document.createElement("td");
td.innerHTML = recordingsBeingPlayed[i].filename.slice(4);
tr.appendChild(td);
td = document.createElement("td");
input = document.createElement("input");
input.setAttribute("type", "button");
input.setAttribute("class", "glyph red");
input.setAttribute("value", HIFI_GLYPH_CLOSE);
input.setAttribute("playerID", recordingsBeingPlayed[i].playerID);
input.addEventListener("click", stopPlayingRecording);
td.appendChild(input);
tr.appendChild(td);
tbody.appendChild(tr);
}
// Empty rows representing available players.
for (i = recordingsBeingPlayed.length, length = numberOfPlayers; i < length; i += 1) {
tr = document.createElement("tr");
td = document.createElement("td");
td.colSpan = 2;
tr.appendChild(td);
tbody.appendChild(tr);
}
// Filler row for extra table space.
tr = document.createElement("tr");
tr.classList.add("filler");
td = document.createElement("td");
td.colSpan = 2;
tr.appendChild(td);
tbody.appendChild(tr);
// Update table content.
elRecordingsTable.replaceChild(tbody, elRecordingsList);
elRecordingsList = document.getElementById("recordings-list");
// Update header cell widths to match content widths.
ths = document.querySelectorAll("#recordings-table thead th");
tds = document.querySelectorAll("#recordings-table tbody tr:first-child td");
for (i = 0; i < ths.length; i += 1) {
ths[i].width = tds[i].offsetWidth;
}
}
function updateInstructions() {
// Display show/hide instructions buttons if players are available.
if (numberOfPlayers === 0) {
elHideInfoButton.classList.add("hidden");
elShowInfoButton.classList.add("hidden");
} else {
elHideInfoButton.classList.remove("hidden");
elShowInfoButton.classList.remove("hidden");
}
// Display instructions if user requested or no players available.
if (isDisplayingInstructions || numberOfPlayers === 0) {
elRecordingsList.classList.add("hidden");
elInstructions.classList.remove("hidden");
} else {
elInstructions.classList.add("hidden");
elRecordingsList.classList.remove("hidden");
}
}
function showInstructions() {
isDisplayingInstructions = true;
updateInstructions();
}
function hideInstructions() {
isDisplayingInstructions = false;
updateInstructions();
}
function updateLoadButton() {
if (isRecording || numberOfPlayers <= recordingsBeingPlayed.length) {
elLoadButton.setAttribute("disabled", "disabled");
} else {
elLoadButton.removeAttribute("disabled");
}
}
function updateSpinner() {
if (isRecording) {
elRecordings.classList.add("hidden");
elSpinner.classList.remove("hidden");
} else {
elSpinner.classList.add("hidden");
elRecordings.classList.remove("hidden");
}
}
function updateFinishOnOpenLabel() {
var WINDOW_FINISH_ON_OPEN_LABEL = "Stop recording automatically when reopen this window",
TABLET_FINISH_ON_OPEN_LABEL = "Stop recording automatically when reopen tablet or window";
elFinishOnOpenLabel.innerHTML = isUsingToolbar ? WINDOW_FINISH_ON_OPEN_LABEL : TABLET_FINISH_ON_OPEN_LABEL;
}
function onScriptEventReceived(data) {
var message = JSON.parse(data);
if (message.type === EVENT_BRIDGE_TYPE) {
switch (message.action) {
case USING_TOOLBAR_ACTION:
isUsingToolbar = message.value;
updateFinishOnOpenLabel();
break;
case FINISH_ON_OPEN_ACTION:
elFinishOnOpen.checked = message.value;
break;
case START_RECORDING_ACTION:
isRecording = true;
elRecordButton.value = "Stop";
updateSpinner();
updateLoadButton();
break;
case SET_COUNTDOWN_NUMBER_ACTION:
elCountdownNumber.innerHTML = message.value;
break;
case STOP_RECORDING_ACTION:
isRecording = false;
elRecordButton.value = "Record";
updateSpinner();
updateLoadButton();
break;
case RECORDINGS_BEING_PLAYED_ACTION:
recordingsBeingPlayed = JSON.parse(message.value);
updateRecordings();
updatePlayersUnused();
updateInstructions();
updateLoadButton();
break;
case NUMBER_OF_PLAYERS_ACTION:
numberOfPlayers = message.value;
updateRecordings();
updatePlayersUnused();
updateInstructions();
updateLoadButton();
break;
}
}
}
function onLoadButtonClicked() {
EventBridge.emitWebEvent(JSON.stringify({
type: EVENT_BRIDGE_TYPE,
action: LOAD_RECORDING_ACTION
}));
}
function onRecordButtonClicked() {
if (!isRecording) {
elRecordButton.value = "Stop";
EventBridge.emitWebEvent(JSON.stringify({
type: EVENT_BRIDGE_TYPE,
action: START_RECORDING_ACTION
}));
isRecording = true;
updateSpinner();
updateLoadButton();
} else {
elRecordButton.value = "Record";
EventBridge.emitWebEvent(JSON.stringify({
type: EVENT_BRIDGE_TYPE,
action: STOP_RECORDING_ACTION
}));
isRecording = false;
updateSpinner();
updateLoadButton();
}
}
function onFinishOnOpenClicked() {
EventBridge.emitWebEvent(JSON.stringify({
type: EVENT_BRIDGE_TYPE,
action: FINISH_ON_OPEN_ACTION,
value: elFinishOnOpen.checked
}));
}
function signalBodyLoaded() {
var EVENTBRIDGE_OPEN_DELAY = 500; // Delay required to ensure EventBridge is ready for use.
setTimeout(function () {
EventBridge.scriptEventReceived.connect(onScriptEventReceived);
EventBridge.emitWebEvent(JSON.stringify({
type: EVENT_BRIDGE_TYPE,
action: BODY_LOADED_ACTION
}));
}, EVENTBRIDGE_OPEN_DELAY);
}
function onBodyLoaded() {
elRecordings = document.getElementById("recordings");
elRecordingsTable = document.getElementById("recordings-table");
elRecordingsList = document.getElementById("recordings-list");
elInstructions = document.getElementById("instructions");
elPlayersUnused = document.getElementById("players-unused");
elHideInfoButton = document.getElementById("hide-info-button");
elHideInfoButton.onclick = hideInstructions;
elShowInfoButton = document.getElementById("show-info-button");
elShowInfoButton.onclick = showInstructions;
elLoadButton = document.getElementById("load-button");
elLoadButton.onclick = onLoadButtonClicked;
elSpinner = document.getElementById("spinner");
elCountdownNumber = document.getElementById("countdown-number");
elRecordButton = document.getElementById("record-button");
elRecordButton.onclick = onRecordButtonClicked;
elFinishOnOpen = document.getElementById("finish-on-open");
elFinishOnOpen.onclick = onFinishOnOpenClicked;
elFinishOnOpenLabel = document.getElementById("finish-on-open-label");
signalBodyLoaded();
}

View file

@ -0,0 +1,87 @@
<!--
// record.html
//
// Created by David Rowe on 5 Apr 2017.
// Copyright 2017 High Fidelity, Inc.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
-->
<html>
<head>
<title>Record</title>
<link rel="stylesheet" type="text/css" href="css/edit-style.css">
<link rel="stylesheet" type="text/css" href="css/record.css">
<script type="text/javascript" src="js/record.js"></script>
</head>
<body>
<div class="title">
<label>Record</label>
</div>
<hr />
<div id="recordings">
<div id="table-container">
<table id="recordings-table">
<colgroup>
<col />
<col id="unload-column" />
</colgroup>
<thead>
<tr>
<th>Recordings Being Played</th>
<th>Unload</th>
</tr>
</thead>
<tbody id="recordings-list"></tbody>
<tbody id="instructions" class="hidden">
<tr>
<td colspan="2">
<p>This app lets you record and play back multiple instances of your avatar in your Sandbox.</p>
<h1>Setup Instructions</h1>
<p>In your sandbox domain:</p>
<ul>
<li>Right-click the High Fidelity Sandbox icon in your system tray and click &ldquo;Settings&rdquo;.</li>
<li>In the &ldquo;Scripts&rdquo; section add a new row and paste in this script URL:<br />
<input type="text" value="https://content.highfidelity.com/Scripts/playRecordingAC.js" readonly />
</li>
<li>Set the number of recordings you&rsquo;d like to run at a given time in the &ldquo;Instances&rdquo; slot.</li>
<li>Click &ldquo;Save and restart&rdquo;.</li>
</ul>
<p>Now you can record and play back recordings in your domain!</p>
<p><input id="hide-info-button" type="button" class="blue" value="Got It" /></p>
</td>
</tr>
</tbody>
<tfoot>
<tr>
<td id="footer-text" colspan="2">
Number of available instances: <span id="players-unused"></span>
<span id="show-info-button">[</span>
</td>
</tr>
</tfoot>
</table>
</div>
</div>
<div id="spinner" class="hidden">
<img src="img/loader-red-countdown-ring.gif" />
<span id="countdown-number">3</span>
</div>
<div id="record-controls">
<div id="load-container">
<input id="load-button" type="button" value="Load" disabled />
</div>
<div id="record-container">
<input id="record-button" class="red" type="button" value="Record" />
</div>
<div id="checkbox-container" class="property checkbox">
<input type="checkbox" id="finish-on-open">
<label for="finish-on-open" id="finish-on-open-label">Stop recording automatically when ...</label>
</div>
</div>
<script>
onBodyLoaded();
</script>
</body>
</html>

View file

@ -0,0 +1,36 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 19.2.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
viewBox="0 0 50 50" style="enable-background:new 0 0 50 50;" xml:space="preserve">
<style type="text/css">
.st0{fill:#FFFFFF;}
</style>
<g id="Layer_2">
</g>
<g>
<path class="st0" d="M23.2,20.5c-1,0.8-1.8,1.4-2.7,2.1c-0.2,0.1-0.2,0.4-0.2,0.7c-0.3,1.7-0.6,3.4-0.9,5.1
c-0.1,0.8-0.6,1.2-1.3,1.1c-0.7-0.1-1.2-0.7-1.1-1.4c0.3-2.2,0.6-4.4,1-6.6c0.1-0.3,0.3-0.7,0.6-0.9c1.4-1.3,2.8-2.5,4.2-3.7
c0.7-0.6,1.5-1,2.4-0.9c0.3,0,0.7,0,1,0c1-0.1,1.7,0.4,2.1,1.3c0.7,1.4,1.4,2.8,1.9,4.3c0.5,1.3,1.2,2.1,2.4,2.6c1,0.4,2,1,3,1.5
c0.2,0.1,0.5,0.3,0.7,0.5c0.4,0.4,0.5,1,0.3,1.4C36.4,28,36,28.1,35.5,28c-0.4-0.1-0.8-0.2-1.2-0.4c-1.3-0.6-2.5-1.2-3.7-1.8
c-0.8-0.3-1.4-0.8-1.8-1.6c-0.2-0.3-0.4-0.6-0.7-1c-0.1,0.3-0.1,0.5-0.2,0.7c-0.3,1.2-0.5,2.4-0.8,3.6c-0.1,0.4,0,0.7,0.2,1
c2.2,3.7,4.4,7.4,6.6,11.1c0.3,0.4,0.4,1,0.5,1.5c0.1,0.7-0.1,1.3-0.7,1.6c-0.7,0.4-1.4,0.4-1.9-0.1c-0.4-0.4-0.8-0.8-1.1-1.3
c-2.5-3.9-5-7.9-7.5-11.8c-0.4-0.7-0.8-1.5-1.1-2.2c-0.1-0.4-0.1-0.9,0-1.3C22.5,24.2,22.8,22.4,23.2,20.5z"/>
<path class="st0" d="M23.2,33.9c-0.1-0.1-0.2-0.2-0.2-0.3c0,0,0,0,0,0c-0.2-0.2-0.3-0.5-0.5-0.7c-0.3-0.4-0.6-0.8-0.9-1.1
c-0.3,1-0.5,2-0.8,3c-0.1,0.3-0.3,0.7-0.4,1c-1,1.5-2,3.1-3,4.6c-0.2,0.4-0.4,0.8-0.6,1.3c-0.2,0.9,0.7,1.9,1.6,1.5
c0.5-0.2,1-0.7,1.3-1.1c0.9-1.1,1.6-2.3,2.5-3.3c0.8-1,1.4-2.2,1.8-3.4C23.8,34.7,23.5,34.3,23.2,33.9z"/>
<path class="st0" d="M29,11.6c0,1.3-1.1,2.4-2.4,2.4h-0.2c-1.3,0-2.4-1.1-2.4-2.4v-1.2C24,9.1,25.1,8,26.4,8h0.2
c1.3,0,2.4,1.1,2.4,2.4V11.6z"/>
<path class="st0" d="M43.4,24.1c-0.5,0.3-0.9,0.5-1.4,0.8v6.3h2.3v-7.6C44,23.8,43.7,23.9,43.4,24.1z"/>
<path class="st0" d="M42,38.6v0.4c0,1.2-1,2.1-2.1,2.1h-0.8v2.3h0.8c2.5,0,4.5-2,4.5-4.5v-0.4H42z"/>
<path class="st0" d="M9.7,12.2v-0.4c0-1.2,1-2.1,2.1-2.1h2V7.3h-2c-2.5,0-4.5,2-4.5,4.5v0.4H9.7z"/>
<rect x="7.4" y="18.3" class="st0" width="2.3" height="12.9"/>
<path class="st0" d="M9.7,38.9v-0.4H7.4v0.4c0,2.5,2,4.5,4.5,4.5h2v-2.3h-2C10.7,41.1,9.7,40.1,9.7,38.9z"/>
<g>
<circle class="st0" cx="38.6" cy="13.3" r="2.2"/>
<path class="st0" d="M38.6,15.5c-1.2,0-2.2-1-2.2-2.2s1-2.2,2.2-2.2c1.2,0,2.2,1,2.2,2.2S39.8,15.5,38.6,15.5z M38.6,11.2
c-1.1,0-2.1,0.9-2.1,2.1s0.9,2.1,2.1,2.1c1.1,0,2.1-0.9,2.1-2.1S39.7,11.2,38.6,11.2z"/>
</g>
<path class="st0" d="M38.6,19.7c-3.6,0-6.4-2.9-6.4-6.4s2.9-6.4,6.4-6.4c3.6,0,6.4,2.9,6.4,6.4S42.1,19.7,38.6,19.7z M38.6,9.1
c-2.3,0-4.2,1.9-4.2,4.2s1.9,4.2,4.2,4.2c2.3,0,4.2-1.9,4.2-4.2S40.9,9.1,38.6,9.1z"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 2.6 KiB

View file

@ -0,0 +1,511 @@
"use strict";
//
// playRecordingAC.js
//
// Created by David Rowe on 7 Apr 2017.
// Copyright 2017 High Fidelity, Inc.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
(function () {
var APP_NAME = "PLAYBACK",
HIFI_RECORDER_CHANNEL = "HiFi-Recorder-Channel",
RECORDER_COMMAND_ERROR = "error",
HIFI_PLAYER_CHANNEL = "HiFi-Player-Channel",
PLAYER_COMMAND_PLAY = "play",
PLAYER_COMMAND_STOP = "stop",
heartbeatTimer = null,
HEARTBEAT_INTERVAL = 3000,
TIMESTAMP_UPDATE_INTERVAL = 2500,
AUTOPLAY_SEARCH_INTERVAL = 5000,
AUTOPLAY_ERROR_INTERVAL = 30000, // 30s
scriptUUID,
Entity,
Player;
function log(message) {
print(APP_NAME + " " + scriptUUID + ": " + message);
}
Entity = (function () {
// Persistence of playback via invisible entity.
var entityID = null,
userData,
updateTimestampTimer = null,
ENTITY_NAME = "Recording",
ENTITY_DESCRIPTION = "Avatar recording to play back",
ENTITIY_POSITION = { x: -16382, y: -16382, z: -16382 }, // Near but not right on domain corner.
ENTITY_SEARCH_DELTA = { x: 1, y: 1, z: 1 }, // Allow for position imprecision.
SEARCH_IDLE = 0,
SEARCH_SEARCHING = 1,
SEARCH_CLAIMING = 2,
SEARCH_PAUSING = 3,
searchState = SEARCH_IDLE,
otherPlayersPlaying,
otherPlayersPlayingCounts,
pauseCount,
isDestroyLater = false,
destroy;
function onUpdateTimestamp() {
if (isDestroyLater) {
destroy();
return;
}
userData.timestamp = Date.now();
Entities.editEntity(entityID, { userData: JSON.stringify(userData) });
EntityViewer.queryOctree(); // Keep up to date ready for find().
}
function id() {
return entityID;
}
function randomInt(min, max) {
return Math.floor(Math.random() * (max - min + 1)) + min;
}
function onMessageReceived(channel, message, sender) {
var index;
if (sender !== scriptUUID) {
message = JSON.parse(message);
if (message.playing !== undefined) {
index = otherPlayersPlaying.indexOf(message.entity);
if (index !== -1) {
otherPlayersPlayingCounts[index] += 1;
} else {
otherPlayersPlaying.push(message.entity);
otherPlayersPlayingCounts.push(1);
}
}
}
}
function create(filename, position, orientation) {
// Create a new persistence entity (even if already have one but that should never occur).
var properties;
log("Create recording entity for " + filename);
if (updateTimestampTimer !== null) { // Just in case.
Script.clearInterval(updateTimestampTimer);
updateTimestampTimer = null;
}
searchState = SEARCH_IDLE;
userData = {
recording: filename,
position: position,
orientation: orientation,
scriptUUID: scriptUUID,
timestamp: Date.now()
};
properties = {
type: "Box",
name: ENTITY_NAME,
description: ENTITY_DESCRIPTION,
position: ENTITIY_POSITION,
visible: false,
userData: JSON.stringify(userData)
};
entityID = Entities.addEntity(properties);
if (!Uuid.isNull(entityID)) {
updateTimestampTimer = Script.setInterval(onUpdateTimestamp, TIMESTAMP_UPDATE_INTERVAL);
return true;
}
log("Could not create recording entity for " + filename);
return false;
}
function find() {
// Find a persistence entity that isn't being played.
// AC scripts may simultaneously find the same entity to play because octree updates aren't instantaneously
// propagated. Additionally, messages are not instantaneous. To address these issues the "find" progresses through
// the following search states:
// - SEARCH_IDLE
// No searching is being performed.
// Return null.
// - SEARCH_SEARCHING
// Looking for an entity that isn't being played (as reported in entity properties) and isn't being claimed (as
// reported by heartbeat messages. If one is found transition to SEARCH_CLAIMING and start reporting the entity
// in heartbeat messages.
// Return null.
// - SEARCH_CLAIMING
// An entity has been found and is reported in heartbeat messages but isn't being played yet. After a period of
// time, if no other players report they're playing that entity then transition to SEARCH_IDLE otherwise
// transition to SEARCH_PAUSING.
// If transitioning to SEARCH_IDLE update the entity userData and return the recording details, otherwise
// return null;
// - SEARCH_PAUSING
// Two or more players have tried to play the same entity. Wait for a randomized period of time before
// transitioning to SEARCH_SEARCHING.
// Return null.
// One of these states is processed each find() call.
var entityIDs,
index,
found = false,
properties,
numberOfClaims,
result = null;
switch (searchState) {
case SEARCH_IDLE:
log("Start searching");
otherPlayersPlaying = [];
otherPlayersPlayingCounts = [];
Messages.subscribe(HIFI_RECORDER_CHANNEL);
Messages.messageReceived.connect(onMessageReceived);
searchState = SEARCH_SEARCHING;
break;
case SEARCH_SEARCHING:
// Find an entity that isn't being played or claimed.
entityIDs = Entities.findEntities(ENTITIY_POSITION, ENTITY_SEARCH_DELTA.x);
if (entityIDs.length > 0) {
index = -1;
while (!found && index < entityIDs.length - 1) {
index += 1;
if (otherPlayersPlaying.indexOf(entityIDs[index]) === -1) {
properties = Entities.getEntityProperties(entityIDs[index], ["name", "userData"]);
userData = JSON.parse(properties.userData);
found = properties.name === ENTITY_NAME && userData.recording !== undefined;
}
}
}
// Claim entity if found.
if (found) {
log("Claim entity " + entityIDs[index]);
entityID = entityIDs[index];
searchState = SEARCH_CLAIMING;
}
break;
case SEARCH_CLAIMING:
// How many other players are claiming (or playing) this entity?
index = otherPlayersPlaying.indexOf(entityID);
numberOfClaims = index !== -1 ? otherPlayersPlayingCounts[index] : 0;
// Have found an entity to play if no other players are also claiming it.
if (numberOfClaims === 0) {
log("Complete claim " + entityID);
Messages.messageReceived.disconnect(onMessageReceived);
Messages.unsubscribe(HIFI_RECORDER_CHANNEL);
searchState = SEARCH_IDLE;
userData.scriptUUID = scriptUUID;
userData.timestamp = Date.now();
Entities.editEntity(entityID, { userData: JSON.stringify(userData) });
updateTimestampTimer = Script.setInterval(onUpdateTimestamp, TIMESTAMP_UPDATE_INTERVAL);
result = { recording: userData.recording, position: userData.position, orientation: userData.orientation };
break;
}
// Otherwise back off for a bit before resuming search.
log("Release claim " + entityID + " and pause searching");
entityID = null;
pauseCount = randomInt(0, otherPlayersPlaying.length);
searchState = SEARCH_PAUSING;
break;
case SEARCH_PAUSING:
// Resume searching if have paused long enough.
pauseCount -= 1;
if (pauseCount < 0) {
log("Resume searching");
otherPlayersPlaying = [];
otherPlayersPlayingCounts = [];
searchState = SEARCH_SEARCHING;
}
break;
}
EntityViewer.queryOctree();
return result;
}
destroy = function () {
// Delete current persistence entity.
if (entityID !== null) { // Just in case.
Entities.deleteEntity(entityID);
entityID = null;
searchState = SEARCH_IDLE;
}
if (updateTimestampTimer !== null) { // Just in case.
Script.clearInterval(updateTimestampTimer);
updateTimestampTimer = null;
}
};
function destroyLater() {
// Schedules a call to destroy() when timer threading suits.
isDestroyLater = true;
}
function setUp() {
// Set up EntityViewer so that can do Entities.findEntities().
// Position and orientation set so that viewing entities only in corner of domain.
var entityViewerPosition = Vec3.sum(ENTITIY_POSITION, ENTITY_SEARCH_DELTA);
EntityViewer.setPosition(entityViewerPosition);
EntityViewer.setOrientation(Quat.lookAtSimple(entityViewerPosition, ENTITIY_POSITION));
EntityViewer.queryOctree();
}
function tearDown() {
// Nothing to do.
}
return {
id: id,
create: create,
find: find,
destroy: destroy,
destroyLater: destroyLater,
setUp: setUp,
tearDown: tearDown
};
}());
Player = (function () {
// Recording playback functions.
var userID = null,
isPlayingRecording = false,
recordingFilename = "",
autoPlayTimer = null,
autoPlay,
playRecording;
function error(message) {
// Send error message to user.
Messages.sendMessage(HIFI_RECORDER_CHANNEL, JSON.stringify({
command: RECORDER_COMMAND_ERROR,
user: userID,
message: message
}));
}
function play(user, recording, position, orientation) {
var errorMessage;
if (autoPlayTimer) { // Cancel auto-play.
// FIXME: Once in a while Script.clearTimeout() fails.
// [DEBUG] [hifi.scriptengine] [3748] [agent] stopTimer -- not in _timerFunctionMap QObject(0x0)
Script.clearTimeout(autoPlayTimer);
autoPlayTimer = null;
}
userID = user;
if (Entity.create(recording, position, orientation)) {
log("Play recording " + recording);
isPlayingRecording = true; // Immediate feedback.
recordingFilename = recording;
playRecording(recordingFilename, position, orientation, true);
} else {
errorMessage = "Could not persist recording " + recording.slice(4); // Remove leading "atp:".
log(errorMessage);
error(errorMessage);
autoPlayTimer = Script.setTimeout(autoPlay, AUTOPLAY_ERROR_INTERVAL); // Resume auto-play later.
}
}
autoPlay = function () {
var recording,
AUTOPLAY_SEARCH_DELTA = 1000;
// Random delay to help reduce collisions between AC scripts.
Script.setTimeout(function () {
// Guard against Script.clearTimeout() in play() not always working.
if (isPlayingRecording) {
return;
}
recording = Entity.find();
if (recording) {
log("Play persisted recording " + recording.recording);
userID = null;
autoPlayTimer = null;
isPlayingRecording = true; // Immediate feedback.
recordingFilename = recording.recording;
playRecording(recording.recording, recording.position, recording.orientation, false);
} else {
autoPlayTimer = Script.setTimeout(autoPlay, AUTOPLAY_SEARCH_INTERVAL); // Try again soon.
}
}, Math.random() * AUTOPLAY_SEARCH_DELTA);
};
playRecording = function (recording, position, orientation, isManual) {
Recording.loadRecording(recording, function (success) {
var errorMessage;
if (success) {
Users.disableIgnoreRadius();
Agent.isAvatar = true;
Avatar.position = position;
Avatar.orientation = orientation;
Recording.setPlayFromCurrentLocation(true);
Recording.setPlayerUseDisplayName(true);
Recording.setPlayerUseHeadModel(false);
Recording.setPlayerUseAttachments(true);
Recording.setPlayerLoop(true);
Recording.setPlayerUseSkeletonModel(true);
Recording.setPlayerTime(0.0);
Recording.startPlaying();
UserActivityLogger.logAction("playRecordingAC_play_recording");
} else {
if (isManual) {
// Delete persistence entity if manual play request.
Entity.destroyLater(); // Schedule for deletion; works around timer threading issues.
}
errorMessage = "Could not load recording " + recording.slice(4); // Remove leading "atp:".
log(errorMessage);
error(errorMessage);
isPlayingRecording = false;
recordingFilename = "";
autoPlayTimer = Script.setTimeout(autoPlay, AUTOPLAY_ERROR_INTERVAL); // Try again later.
}
});
};
function stop() {
log("Stop playing " + recordingFilename);
Entity.destroy();
if (Recording.isPlaying()) {
Recording.stopPlaying();
Agent.isAvatar = false;
}
isPlayingRecording = false;
recordingFilename = "";
}
function isPlaying() {
return isPlayingRecording;
}
function recording() {
return recordingFilename;
}
function setUp() {
Entity.setUp();
}
function tearDown() {
if (autoPlayTimer) {
Script.clearTimeout(autoPlayTimer);
autoPlayTimer = null;
}
Entity.tearDown();
}
return {
autoPlay: autoPlay,
play: play,
stop: stop,
isPlaying: isPlaying,
recording: recording,
setUp: setUp,
tearDown: tearDown
};
}());
function sendHeartbeat() {
Messages.sendMessage(HIFI_RECORDER_CHANNEL, JSON.stringify({
playing: Player.isPlaying(),
recording: Player.recording(),
entity: Entity.id()
}));
}
function onHeartbeatTimer() {
sendHeartbeat();
heartbeatTimer = Script.setTimeout(onHeartbeatTimer, HEARTBEAT_INTERVAL);
}
function startHeartbeat() {
onHeartbeatTimer();
}
function stopHeartbeat() {
if (heartbeatTimer) {
Script.clearTimeout(heartbeatTimer);
heartbeatTimer = null;
}
}
function onMessageReceived(channel, message, sender) {
if (channel !== HIFI_PLAYER_CHANNEL) {
return;
}
message = JSON.parse(message);
if (message.player === scriptUUID) {
switch (message.command) {
case PLAYER_COMMAND_PLAY:
if (!Player.isPlaying()) {
Player.play(sender, message.recording, message.position, message.orientation);
} else {
log("Didn't start playing " + message.recording + " because already playing " + Player.recording());
}
sendHeartbeat();
break;
case PLAYER_COMMAND_STOP:
Player.stop();
Player.autoPlay(); // There may be another recording to play.
sendHeartbeat();
break;
}
}
}
function setUp() {
scriptUUID = Agent.sessionUUID;
Player.setUp();
Messages.messageReceived.connect(onMessageReceived);
Messages.subscribe(HIFI_PLAYER_CHANNEL);
Player.autoPlay();
startHeartbeat();
UserActivityLogger.logAction("playRecordingAC_script_load");
}
function tearDown() {
stopHeartbeat();
Player.stop();
Messages.messageReceived.disconnect(onMessageReceived);
Messages.unsubscribe(HIFI_PLAYER_CHANNEL);
Player.tearDown();
}
setUp();
Script.scriptEnding.connect(tearDown);
}());

View file

@ -0,0 +1,705 @@
"use strict";
//
// record.js
//
// Created by David Rowe on 5 Apr 2017.
// Copyright 2017 High Fidelity, Inc.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
(function () {
var APP_NAME = "RECORD",
APP_ICON_INACTIVE = Script.resolvePath("assets/avatar-record-i.svg"),
APP_ICON_ACTIVE = Script.resolvePath("assets/avatar-record-a.svg"),
APP_URL = Script.resolvePath("html/record.html"),
isDialogDisplayed = false,
tablet,
button,
isConnected,
RecordingIndicator,
Recorder,
Player,
Dialog,
SCRIPT_STARTUP_DELAY = 3000; // 3s
function log(message) {
print(APP_NAME + ": " + message);
}
function error(message, info) {
print(APP_NAME + ": " + message + (info !== undefined ? " - " + info : ""));
Window.alert(message);
}
function logDetails() {
return {
current_domain: location.placename
};
}
RecordingIndicator = (function () {
// Displays "recording" overlay.
var hmdOverlay,
HMD_FONT_SIZE = 0.08,
desktopOverlay,
DESKTOP_FONT_SIZE = 24;
function show() {
// Create both overlays in case user switches desktop/HMD mode.
var screenSize = Controller.getViewportDimensions(),
recordingText = "REC", // Unicode circle \u25cf doesn't render in HMD.
CAMERA_JOINT_INDEX = -7;
if (HMD.active) {
// 3D overlay attached to avatar.
hmdOverlay = Overlays.addOverlay("text3d", {
text: recordingText,
dimensions: { x: 3 * HMD_FONT_SIZE, y: HMD_FONT_SIZE },
parentID: MyAvatar.sessionUUID,
parentJointIndex: CAMERA_JOINT_INDEX,
localPosition: { x: 0.95, y: 0.95, z: -2.0 },
color: { red: 255, green: 0, blue: 0 },
alpha: 0.9,
lineHeight: HMD_FONT_SIZE,
backgroundAlpha: 0,
ignoreRayIntersection: true,
isFacingAvatar: true,
drawInFront: true,
visible: true
});
} else {
// 2D overlay on desktop.
desktopOverlay = Overlays.addOverlay("text", {
text: recordingText,
width: 3 * DESKTOP_FONT_SIZE,
height: DESKTOP_FONT_SIZE,
x: screenSize.x - 4 * DESKTOP_FONT_SIZE,
y: DESKTOP_FONT_SIZE,
font: { size: DESKTOP_FONT_SIZE },
color: { red: 255, green: 8, blue: 8 },
alpha: 1.0,
backgroundAlpha: 0,
visible: true
});
}
}
function hide() {
if (desktopOverlay) {
Overlays.deleteOverlay(desktopOverlay);
}
if (hmdOverlay) {
Overlays.deleteOverlay(hmdOverlay);
}
}
return {
show: show,
hide: hide
};
}());
Recorder = (function () {
// Makes the recording and uploads it to the domain's Asset Server.
var IDLE = 0,
COUNTING_DOWN = 1,
RECORDING = 2,
recordingState = IDLE,
mappingPath,
startPosition,
startOrientation,
play,
countdownTimer,
countdownSeconds,
COUNTDOWN_SECONDS = 3,
tickSound,
startRecordingSound,
finishRecordingSound,
TICK_SOUND = "assets/sounds/countdown-tick.wav",
START_RECORDING_SOUND = "assets/sounds/start-recording.wav",
FINISH_RECORDING_SOUND = "assets/sounds/finish-recording.wav",
START_RECORDING_SOUND_DURATION = 1200,
SOUND_VOLUME = 0.2;
function playSound(sound) {
Audio.playSound(sound, {
position: MyAvatar.position,
localOnly: true,
volume: SOUND_VOLUME
});
}
function setMappingCallback(status) {
// FIXME: "" is for RC <= 63, null is for RC > 63. Remove the former when RC63 is no longer used.
if (status !== null && status !== "") {
error("Error mapping recording to " + mappingPath + " on Asset Server!", status);
return;
}
log("Recording mapped to " + mappingPath);
log("Request play recording");
play("atp:" + mappingPath, startPosition, startOrientation);
}
function saveRecordingToAssetCallback(url) {
var filename,
hash;
if (url === "") {
error("Error saving recording to Asset Server!");
return;
}
log("Recording saved to Asset Server as " + url);
filename = (new Date()).toISOString(); // yyyy-mm-ddThh:mm:ss.sssZ
filename = filename.replace(/[\-:]|\.\d*Z$/g, "").replace("T", "-") + ".hfr"; // yyyymmmdd-hhmmss.hfr
hash = url.slice(4); // Remove leading "atp:" from url.
mappingPath = "/recordings/" + filename;
Assets.setMapping(mappingPath, hash, setMappingCallback);
}
function startRecording() {
recordingState = RECORDING;
log("Start recording");
playSound(startRecordingSound);
Script.setTimeout(function () {
// Delay start so that start beep is not included in recorded sound.
startPosition = MyAvatar.position;
startOrientation = MyAvatar.orientation;
Recording.startRecording();
RecordingIndicator.show();
}, START_RECORDING_SOUND_DURATION);
}
function finishRecording() {
var success,
error;
recordingState = IDLE;
log("Finish recording");
UserActivityLogger.logAction("record_finish_recording", logDetails());
playSound(finishRecordingSound);
Recording.stopRecording();
RecordingIndicator.hide();
success = Recording.saveRecordingToAsset(saveRecordingToAssetCallback);
if (!success) {
error("Error saving recording to Asset Server!");
}
}
function cancelRecording() {
Recording.stopRecording();
RecordingIndicator.hide();
recordingState = IDLE;
log("Cancel recording");
}
function finishCountdown() {
Dialog.setCountdownNumber("");
recordingState = RECORDING;
startRecording();
}
function cancelCountdown() {
recordingState = IDLE;
Script.clearInterval(countdownTimer);
Dialog.setCountdownNumber("");
log("Cancel countdown");
}
function startCountdown() {
recordingState = COUNTING_DOWN;
log("Start countdown");
countdownSeconds = COUNTDOWN_SECONDS;
Dialog.setCountdownNumber(countdownSeconds);
playSound(tickSound);
countdownTimer = Script.setInterval(function () {
countdownSeconds -= 1;
if (countdownSeconds <= 0) {
Script.clearInterval(countdownTimer);
finishCountdown();
} else {
Dialog.setCountdownNumber(countdownSeconds);
playSound(tickSound);
}
}, 1000);
}
function isIdle() {
return recordingState === IDLE;
}
function isCountingDown() {
return recordingState === COUNTING_DOWN;
}
function isRecording() {
return recordingState === RECORDING;
}
function setUp(playerCallback) {
play = playerCallback;
tickSound = SoundCache.getSound(Script.resolvePath(TICK_SOUND));
startRecordingSound = SoundCache.getSound(Script.resolvePath(START_RECORDING_SOUND));
finishRecordingSound = SoundCache.getSound(Script.resolvePath(FINISH_RECORDING_SOUND));
}
function tearDown() {
// Nothing to do; any cancelling of recording needs to be done by script using this object.
}
return {
startCountdown: startCountdown,
cancelCountdown: cancelCountdown,
startRecording: startRecording,
cancelRecording: cancelRecording,
finishRecording: finishRecording,
isIdle: isIdle,
isCountingDown: isCountingDown,
isRecording: isRecording,
setUp: setUp,
tearDown: tearDown
};
}());
Player = (function () {
var HIFI_RECORDER_CHANNEL = "HiFi-Recorder-Channel",
RECORDER_COMMAND_ERROR = "error",
HIFI_PLAYER_CHANNEL = "HiFi-Player-Channel",
PLAYER_COMMAND_PLAY = "play",
PLAYER_COMMAND_STOP = "stop",
playerIDs = [], // UUIDs of AC player scripts.
playerIsPlayings = [], // True if AC player script is playing a recording.
playerRecordings = [], // Assignment client mappings of recordings being played.
playerTimestamps = [], // Timestamps of last heartbeat update from player script.
updateTimer,
UPDATE_INTERVAL = 5000; // Must be > player's HEARTBEAT_INTERVAL.
function numberOfPlayers() {
return playerIDs.length;
}
function updatePlayers() {
var now = Date.now(),
countBefore = playerIDs.length,
i;
// Remove players that haven't sent a heartbeat for a while.
for (i = playerTimestamps.length - 1; i >= 0; i -= 1) {
if (now - playerTimestamps[i] > UPDATE_INTERVAL) {
playerIDs.splice(i, 1);
playerIsPlayings.splice(i, 1);
playerRecordings.splice(i, 1);
playerTimestamps.splice(i, 1);
}
}
// Update UI.
if (playerIDs.length !== countBefore) {
Dialog.updatePlayerDetails(playerIsPlayings, playerRecordings, playerIDs);
}
}
function playRecording(recording, position, orientation) {
var index;
// Optional function parameters.
if (position === undefined) {
position = MyAvatar.position;
}
if (orientation === undefined) {
orientation = MyAvatar.orientation;
}
index = playerIsPlayings.indexOf(false);
if (index === -1) {
error("No player instance available to play recording "
+ recording.slice(4) + "!"); // Remove leading "atp:" from recording.
return;
}
Messages.sendMessage(HIFI_PLAYER_CHANNEL, JSON.stringify({
player: playerIDs[index],
command: PLAYER_COMMAND_PLAY,
recording: recording,
position: position,
orientation: orientation
}));
}
function stopPlayingRecording(playerID) {
Messages.sendMessage(HIFI_PLAYER_CHANNEL, JSON.stringify({
player: playerID,
command: PLAYER_COMMAND_STOP
}));
}
function onMessageReceived(channel, message, sender) {
// Heartbeat from AC script.
var index;
if (channel !== HIFI_RECORDER_CHANNEL) {
return;
}
message = JSON.parse(message);
if (message.command === RECORDER_COMMAND_ERROR) {
if (message.user === MyAvatar.sessionUUID) {
error(message.message);
}
} else {
index = playerIDs.indexOf(sender);
if (index === -1) {
index = playerIDs.length;
playerIDs[index] = sender;
}
playerIsPlayings[index] = message.playing;
playerRecordings[index] = message.recording;
playerTimestamps[index] = Date.now();
Dialog.updatePlayerDetails(playerIsPlayings, playerRecordings, playerIDs);
}
}
function reset() {
playerIDs = [];
playerIsPlayings = [];
playerRecordings = [];
playerTimestamps = [];
Dialog.updatePlayerDetails(playerIsPlayings, playerRecordings, playerIDs);
}
function setUp() {
// Messaging with AC scripts.
Messages.messageReceived.connect(onMessageReceived);
Messages.subscribe(HIFI_RECORDER_CHANNEL);
updateTimer = Script.setInterval(updatePlayers, UPDATE_INTERVAL);
}
function tearDown() {
Script.clearInterval(updateTimer);
Messages.messageReceived.disconnect(onMessageReceived);
Messages.unsubscribe(HIFI_RECORDER_CHANNEL);
}
return {
playRecording: playRecording,
stopPlayingRecording: stopPlayingRecording,
numberOfPlayers: numberOfPlayers,
reset: reset,
setUp: setUp,
tearDown: tearDown
};
}());
Dialog = (function () {
var isFinishOnOpen = false,
countdownNumber = "",
EVENT_BRIDGE_TYPE = "record",
BODY_LOADED_ACTION = "bodyLoaded",
USING_TOOLBAR_ACTION = "usingToolbar",
RECORDINGS_BEING_PLAYED_ACTION = "recordingsBeingPlayed",
NUMBER_OF_PLAYERS_ACTION = "numberOfPlayers",
STOP_PLAYING_RECORDING_ACTION = "stopPlayingRecording",
LOAD_RECORDING_ACTION = "loadRecording",
START_RECORDING_ACTION = "startRecording",
SET_COUNTDOWN_NUMBER_ACTION = "setCountdownNumber",
STOP_RECORDING_ACTION = "stopRecording",
FINISH_ON_OPEN_ACTION = "finishOnOpen",
SETTINGS_FINISH_ON_OPEN = "record/finishOnOpen";
function isUsingToolbar() {
return ((HMD.active && Settings.getValue("hmdTabletBecomesToolbar"))
|| (!HMD.active && Settings.getValue("desktopTabletBecomesToolbar")));
}
function updateRecordingStatus(isRecording) {
if (isRecording) {
tablet.emitScriptEvent(JSON.stringify({
type: EVENT_BRIDGE_TYPE,
action: START_RECORDING_ACTION
}));
tablet.emitScriptEvent(JSON.stringify({
type: EVENT_BRIDGE_TYPE,
action: SET_COUNTDOWN_NUMBER_ACTION,
value: countdownNumber
}));
} else {
tablet.emitScriptEvent(JSON.stringify({
type: EVENT_BRIDGE_TYPE,
action: STOP_RECORDING_ACTION
}));
}
}
function updatePlayerDetails(playerIsPlayings, playerRecordings, playerIDs) {
var recordingsBeingPlayed = [],
length,
i;
for (i = 0, length = playerIsPlayings.length; i < length; i += 1) {
if (playerIsPlayings[i]) {
recordingsBeingPlayed.push({
filename: playerRecordings[i],
playerID: playerIDs[i]
});
}
}
tablet.emitScriptEvent(JSON.stringify({
type: EVENT_BRIDGE_TYPE,
action: RECORDINGS_BEING_PLAYED_ACTION,
value: JSON.stringify(recordingsBeingPlayed)
}));
tablet.emitScriptEvent(JSON.stringify({
type: EVENT_BRIDGE_TYPE,
action: NUMBER_OF_PLAYERS_ACTION,
value: playerIsPlayings.length
}));
}
function setCountdownNumber(number) {
countdownNumber = number;
tablet.emitScriptEvent(JSON.stringify({
type: EVENT_BRIDGE_TYPE,
action: SET_COUNTDOWN_NUMBER_ACTION,
value: countdownNumber
}));
}
function finishOnOpen() {
return isFinishOnOpen;
}
function onAssetsDirChanged(recording) {
Window.assetsDirChanged.disconnect(onAssetsDirChanged);
if (recording !== "") {
log("Load recording " + recording);
UserActivityLogger.logAction("record_load_recording", logDetails());
Player.playRecording("atp:" + recording, MyAvatar.position, MyAvatar.orientation);
}
}
function onWebEventReceived(data) {
var message,
recording;
message = JSON.parse(data);
if (message.type === EVENT_BRIDGE_TYPE) {
switch (message.action) {
case BODY_LOADED_ACTION:
// Dialog's ready; initialize its state.
tablet.emitScriptEvent(JSON.stringify({
type: EVENT_BRIDGE_TYPE,
action: USING_TOOLBAR_ACTION,
value: isUsingToolbar()
}));
tablet.emitScriptEvent(JSON.stringify({
type: EVENT_BRIDGE_TYPE,
action: FINISH_ON_OPEN_ACTION,
value: isFinishOnOpen
}));
tablet.emitScriptEvent(JSON.stringify({
type: EVENT_BRIDGE_TYPE,
action: NUMBER_OF_PLAYERS_ACTION,
value: Player.numberOfPlayers()
}));
updateRecordingStatus(!Recorder.isIdle());
UserActivityLogger.logAction("record_open_dialog", logDetails());
break;
case STOP_PLAYING_RECORDING_ACTION:
// Stop the specified player.
log("Unload recording " + message.value);
Player.stopPlayingRecording(message.value);
break;
case LOAD_RECORDING_ACTION:
// User wants to select an ATP recording to play.
Window.assetsDirChanged.connect(onAssetsDirChanged);
Window.browseAssetsAsync("Select Recording to Play", "recordings", "*.hfr");
break;
case START_RECORDING_ACTION:
// Start making a recording.
if (Recorder.isIdle()) {
Recorder.startCountdown();
}
break;
case STOP_RECORDING_ACTION:
// Cancel or finish a recording.
if (Recorder.isCountingDown()) {
Recorder.cancelCountdown();
} else if (Recorder.isRecording()) {
Recorder.finishRecording();
}
break;
case FINISH_ON_OPEN_ACTION:
// Set behavior on dialog open.
isFinishOnOpen = message.value;
Settings.setValue(SETTINGS_FINISH_ON_OPEN, isFinishOnOpen);
break;
}
}
}
function setUp() {
isFinishOnOpen = Settings.getValue(SETTINGS_FINISH_ON_OPEN) === true;
tablet.webEventReceived.connect(onWebEventReceived);
}
function tearDown() {
tablet.webEventReceived.disconnect(onWebEventReceived);
}
return {
updatePlayerDetails: updatePlayerDetails,
updateRecordingStatus: updateRecordingStatus,
setCountdownNumber: setCountdownNumber,
finishOnOpen: finishOnOpen,
setUp: setUp,
tearDown: tearDown
};
}());
function onTabletScreenChanged(type, url) {
// Opened/closed dialog in tablet or window.
var RECORD_URL = "/html/record.html";
if (type === "Web" && url.slice(-RECORD_URL.length) === RECORD_URL) {
if (Dialog.finishOnOpen()) {
// Cancel countdown or finish recording.
if (Recorder.isCountingDown()) {
Recorder.cancelCountdown();
} else if (Recorder.isRecording()) {
Recorder.finishRecording();
}
Dialog.updateRecordingStatus(false);
}
isDialogDisplayed = true;
} else {
isDialogDisplayed = false;
}
button.editProperties({ isActive: isDialogDisplayed });
}
function onTabletShownChanged() {
// Opened/closed tablet.
if (tablet.tabletShown && Dialog.finishOnOpen()) {
// Cancel countdown or finish recording.
if (Recorder.isCountingDown()) {
Recorder.cancelCountdown();
} else if (Recorder.isRecording()) {
Recorder.finishRecording();
}
Dialog.updateRecordingStatus(false);
}
}
function onButtonClicked() {
if (isDialogDisplayed) {
// Can click icon in toolbar mode; gotoHomeScreen() closes dialog.
tablet.gotoHomeScreen();
isDialogDisplayed = false;
} else {
tablet.gotoWebScreen(APP_URL);
isDialogDisplayed = true;
}
}
function onUpdate() {
if (isConnected !== Window.location.isConnected) {
// Server restarted or domain changed.
isConnected = !isConnected;
if (!isConnected) {
// Clear dialog.
Player.reset();
}
}
}
function setUp() {
tablet = Tablet.getTablet("com.highfidelity.interface.tablet.system");
if (!tablet) {
return;
}
// Tablet/toolbar button.
button = tablet.addButton({
icon: APP_ICON_INACTIVE,
activeIcon: APP_ICON_ACTIVE,
text: APP_NAME,
isActive: false
});
if (button) {
button.clicked.connect(onButtonClicked);
}
// Track showing/hiding tablet/dialog.
tablet.screenChanged.connect(onTabletScreenChanged);
tablet.tabletShownChanged.connect(onTabletShownChanged);
Dialog.setUp();
Player.setUp();
Recorder.setUp(Player.playRecording);
isConnected = Window.location.isConnected;
Script.update.connect(onUpdate);
UserActivityLogger.logAction("record_run_script", logDetails());
}
function tearDown() {
if (!tablet) {
return;
}
Script.update.disconnect(onUpdate);
Recorder.tearDown();
Player.tearDown();
Dialog.tearDown();
tablet.tabletShownChanged.disconnect(onTabletShownChanged);
tablet.screenChanged.disconnect(onTabletScreenChanged);
if (button) {
button.clicked.disconnect(onButtonClicked);
tablet.removeButton(button);
button = null;
}
if (Recorder.isCountingDown()) {
Recorder.cancelCountdown();
} else if (Recorder.isRecording()) {
Recorder.cancelRecording();
}
if (isDialogDisplayed) {
tablet.gotoHomeScreen();
}
tablet = null;
}
// FIXME: If setUp() is run immediately at Interface start-up, Interface hangs and crashes because of the line of code:
// tablet = Tablet.getTablet("com.highfidelity.interface.tablet.system");
//setUp();
//Script.scriptEnding.connect(tearDown);
Script.setTimeout(function () {
setUp();
Script.scriptEnding.connect(tearDown);
}, SCRIPT_STARTUP_DELAY);
}());