From a7cc58245993bfdc3c3cbd6c0b3e54a610fb3711 Mon Sep 17 00:00:00 2001
From: David Rowe <david@ctrlaltstudio.com>
Date: Wed, 10 May 2017 10:32:43 +1200
Subject: [PATCH 01/15] Don't multiply heartbeats

---
 scripts/system/playRecordingAC.js | 11 +++++++----
 1 file changed, 7 insertions(+), 4 deletions(-)

diff --git a/scripts/system/playRecordingAC.js b/scripts/system/playRecordingAC.js
index b4fae9a2e3..6c7a5eb8b1 100644
--- a/scripts/system/playRecordingAC.js
+++ b/scripts/system/playRecordingAC.js
@@ -368,13 +368,16 @@
         };
     }());
 
-    function sendHeartbeat() {
+    function sendHeartbeat(isOneShot) {
         Messages.sendMessage(HIFI_RECORDER_CHANNEL, JSON.stringify({
             playing: Player.isPlaying(),
             recording: Player.recording(),
             entity: Entity.id()
         }));
-        heartbeatTimer = Script.setTimeout(sendHeartbeat, HEARTBEAT_INTERVAL);
+
+        if (!isOneShot) {
+            heartbeatTimer = Script.setTimeout(sendHeartbeat, HEARTBEAT_INTERVAL);
+        }
     }
 
     function stopHeartbeat() {
@@ -398,12 +401,12 @@
                 } else {
                     log("Didn't start playing " + message.recording + " because already playing " + Player.recording());
                 }
-                sendHeartbeat();
+                sendHeartbeat(true);
                 break;
             case PLAYER_COMMAND_STOP:
                 Player.stop();
                 Player.autoPlay();  // There may be another recording to play.
-                sendHeartbeat();
+                sendHeartbeat(true);
                 break;
             }
         }

From 771b76690cff33cfac07f0fbcf4ec72b3c80086b Mon Sep 17 00:00:00 2001
From: David Rowe <david@ctrlaltstudio.com>
Date: Wed, 10 May 2017 10:56:23 +1200
Subject: [PATCH 02/15] Clear timeout that checks recording started when stop
 recording

---
 scripts/system/record.js | 3 ++-
 1 file changed, 2 insertions(+), 1 deletion(-)

diff --git a/scripts/system/record.js b/scripts/system/record.js
index 3db82696ef..f612fea80d 100644
--- a/scripts/system/record.js
+++ b/scripts/system/record.js
@@ -350,7 +350,7 @@
             // Cancel check that recording started playing.
             index = playerIDs.indexOf(playerID);
             if (index !== -1 && playerStartupTimeouts[index] !== null) {
-                // Cannot clearTimeout() without program log error, so just set null.
+                Script.clearTimeout(playerStartupTimeouts[index]);
                 playerStartupTimeouts[index] = null;
             }
 
@@ -374,6 +374,7 @@
             if (index === -1) {
                 index = playerIDs.length;
                 playerIDs[index] = sender;
+                playerStartupTimeouts[index] = null;
             }
             playerIsPlayings[index] = message.playing;
             playerRecordings[index] = message.recording;

From c2d22a1246248e49b9454449df84a6483e02bbf3 Mon Sep 17 00:00:00 2001
From: David Rowe <david@ctrlaltstudio.com>
Date: Wed, 10 May 2017 10:57:53 +1200
Subject: [PATCH 03/15] Log stopping playing recording

---
 scripts/system/record.js | 1 +
 1 file changed, 1 insertion(+)

diff --git a/scripts/system/record.js b/scripts/system/record.js
index f612fea80d..4d5427c874 100644
--- a/scripts/system/record.js
+++ b/scripts/system/record.js
@@ -524,6 +524,7 @@
                     break;
                 case STOP_PLAYING_RECORDING_ACTION:
                     // Stop the specified player.
+                    log("Unload recording " + message.value);
                     Player.stopPlayingRecording(message.value);
                     break;
                 case LOAD_RECORDING_ACTION:

From f74e29e4b0f0546780082571e8819c84c7035ea6 Mon Sep 17 00:00:00 2001
From: David Rowe <david@ctrlaltstudio.com>
Date: Fri, 12 May 2017 11:43:12 +1200
Subject: [PATCH 04/15] Code review

---
 scripts/system/playRecordingAC.js | 19 ++++++++++++-------
 1 file changed, 12 insertions(+), 7 deletions(-)

diff --git a/scripts/system/playRecordingAC.js b/scripts/system/playRecordingAC.js
index 6c7a5eb8b1..8351d6f5e1 100644
--- a/scripts/system/playRecordingAC.js
+++ b/scripts/system/playRecordingAC.js
@@ -368,16 +368,21 @@
         };
     }());
 
-    function sendHeartbeat(isOneShot) {
+    function sendHeartbeat() {
         Messages.sendMessage(HIFI_RECORDER_CHANNEL, JSON.stringify({
             playing: Player.isPlaying(),
             recording: Player.recording(),
             entity: Entity.id()
         }));
+    }
 
-        if (!isOneShot) {
-            heartbeatTimer = Script.setTimeout(sendHeartbeat, HEARTBEAT_INTERVAL);
-        }
+    function onHeartbeatTimer() {
+        sendHeartbeat();
+        heartbeatTimer = Script.setTimeout(onHeartbeatTimer, HEARTBEAT_INTERVAL);
+    }
+
+    function startHeartbeat() {
+        onHeartbeatTimer();
     }
 
     function stopHeartbeat() {
@@ -401,12 +406,12 @@
                 } else {
                     log("Didn't start playing " + message.recording + " because already playing " + Player.recording());
                 }
-                sendHeartbeat(true);
+                sendHeartbeat();
                 break;
             case PLAYER_COMMAND_STOP:
                 Player.stop();
                 Player.autoPlay();  // There may be another recording to play.
-                sendHeartbeat(true);
+                sendHeartbeat();
                 break;
             }
         }
@@ -421,7 +426,7 @@
         Messages.subscribe(HIFI_PLAYER_CHANNEL);
 
         Player.autoPlay();
-        sendHeartbeat();
+        startHeartbeat();
 
         UserActivityLogger.logAction("playRecordingAC_script_load");
     }

From d7a4396daaf7010152c509a97b65c350c837cd09 Mon Sep 17 00:00:00 2001
From: David Rowe <david@ctrlaltstudio.com>
Date: Fri, 12 May 2017 11:45:11 +1200
Subject: [PATCH 05/15] Improve log messages

---
 scripts/system/playRecordingAC.js | 7 ++++---
 1 file changed, 4 insertions(+), 3 deletions(-)

diff --git a/scripts/system/playRecordingAC.js b/scripts/system/playRecordingAC.js
index 8351d6f5e1..e738c01d32 100644
--- a/scripts/system/playRecordingAC.js
+++ b/scripts/system/playRecordingAC.js
@@ -83,7 +83,7 @@
             // Create a new persistence entity (even if already have one but that should never occur).
             var properties;
 
-            log("Create recording " + filename);
+            log("Create recording entity for " + filename);
 
             if (updateTimestampTimer !== null) {
                 Script.clearInterval(updateTimestampTimer);  // Just in case.
@@ -114,6 +114,7 @@
                 return true;
             }
 
+            log("Could not create recording entity for " + filename);
             return false;
         }
 
@@ -269,12 +270,12 @@
 
         function play(recording, position, orientation) {
             if (Entity.create(recording, position, orientation)) {
-                log("Play new recording " + recordingFilename);
+                log("Play recording " + recordingFilename);
                 isPlayingRecording = true;
                 recordingFilename = recording;
                 playRecording(recordingFilename, position, orientation);
             } else {
-                log("Could not create entity to play new recording " + recordingFilename);
+                log("Could not play recording " + recordingFilename);
             }
         }
 

From 26672b475ff280bee0b32b13fa75238cf00a1ba9 Mon Sep 17 00:00:00 2001
From: David Rowe <david@ctrlaltstudio.com>
Date: Fri, 12 May 2017 14:13:32 +1200
Subject: [PATCH 06/15] Clear timer value when timer stopped

---
 scripts/system/playRecordingAC.js | 6 ++++--
 1 file changed, 4 insertions(+), 2 deletions(-)

diff --git a/scripts/system/playRecordingAC.js b/scripts/system/playRecordingAC.js
index e738c01d32..cc4c0345a9 100644
--- a/scripts/system/playRecordingAC.js
+++ b/scripts/system/playRecordingAC.js
@@ -85,8 +85,9 @@
 
             log("Create recording entity for " + filename);
 
-            if (updateTimestampTimer !== null) {
-                Script.clearInterval(updateTimestampTimer);  // Just in case.
+            if (updateTimestampTimer !== null) {  // Just in case.
+                Script.clearInterval(updateTimestampTimer);
+                updateTimestampTimer = null;
             }
 
             searchState = SEARCH_IDLE;
@@ -234,6 +235,7 @@
             }
             if (updateTimestampTimer !== null) {  // Just in case.
                 Script.clearInterval(updateTimestampTimer);
+                updateTimestampTimer = null;
             }
         }
 

From 42aacb07ff4b0e4aaa2dacd671f68ef79ce2877e Mon Sep 17 00:00:00 2001
From: David Rowe <david@ctrlaltstudio.com>
Date: Fri, 12 May 2017 14:14:24 +1200
Subject: [PATCH 07/15] Remove check that recording has started playing

---
 scripts/system/record.js | 24 +-----------------------
 1 file changed, 1 insertion(+), 23 deletions(-)

diff --git a/scripts/system/record.js b/scripts/system/record.js
index 4d5427c874..f9936942fb 100644
--- a/scripts/system/record.js
+++ b/scripts/system/record.js
@@ -277,7 +277,6 @@
             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.
-            playerStartupTimeouts = [], // Timers that check that recording has started playing.
 
             updateTimer,
             UPDATE_INTERVAL = 5000;  // Must be > player's HEARTBEAT_INTERVAL.
@@ -298,7 +297,6 @@
                     playerIsPlayings.splice(i, 1);
                     playerRecordings.splice(i, 1);
                     playerTimestamps.splice(i, 1);
-                    playerStartupTimeouts.splice(i, 1);
                 }
             }
 
@@ -309,8 +307,7 @@
         }
 
         function playRecording(recording, position, orientation) {
-            var index,
-                CHECK_PLAYING_TIMEOUT = 10000;
+            var index;
 
             // Optional function parameters.
             if (position === undefined) {
@@ -334,26 +331,9 @@
                 position: position,
                 orientation: orientation
             }));
-
-            playerStartupTimeouts[index] = Script.setTimeout(function () {
-                if ((!playerIsPlayings[index] || playerRecordings[index] !== recording) && playerStartupTimeouts[index]) {
-                    error("Didn't start playing recording "
-                        + recording.slice(4) + "!");  // Remove leading "atp:" from recording.
-                }
-                playerStartupTimeouts[index] = null;
-            }, CHECK_PLAYING_TIMEOUT);
         }
 
         function stopPlayingRecording(playerID) {
-            var index;
-
-            // Cancel check that recording started playing.
-            index = playerIDs.indexOf(playerID);
-            if (index !== -1 && playerStartupTimeouts[index] !== null) {
-                Script.clearTimeout(playerStartupTimeouts[index]);
-                playerStartupTimeouts[index] = null;
-            }
-
             Messages.sendMessage(HIFI_PLAYER_CHANNEL, JSON.stringify({
                 player: playerID,
                 command: PLAYER_COMMAND_STOP
@@ -374,7 +354,6 @@
             if (index === -1) {
                 index = playerIDs.length;
                 playerIDs[index] = sender;
-                playerStartupTimeouts[index] = null;
             }
             playerIsPlayings[index] = message.playing;
             playerRecordings[index] = message.recording;
@@ -387,7 +366,6 @@
             playerIsPlayings = [];
             playerRecordings = [];
             playerTimestamps = [];
-            playerStartupTimeouts = [];
             Dialog.updatePlayerDetails(playerIsPlayings, playerRecordings, playerIDs);
         }
 

From acd71caae273b973dc64b66072bb4afa3aae9efc Mon Sep 17 00:00:00 2001
From: David Rowe <david@ctrlaltstudio.com>
Date: Fri, 12 May 2017 14:53:38 +1200
Subject: [PATCH 08/15] Inform user when an AC error prevents a recording from
 playing

---
 scripts/system/playRecordingAC.js | 37 +++++++++++++++++++++++--------
 scripts/system/record.js          | 21 +++++++++++-------
 2 files changed, 41 insertions(+), 17 deletions(-)

diff --git a/scripts/system/playRecordingAC.js b/scripts/system/playRecordingAC.js
index cc4c0345a9..5dab0c2ba0 100644
--- a/scripts/system/playRecordingAC.js
+++ b/scripts/system/playRecordingAC.js
@@ -14,6 +14,7 @@
 
     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",
@@ -69,12 +70,14 @@
 
             if (sender !== scriptUUID) {
                 message = JSON.parse(message);
-                index = otherPlayersPlaying.indexOf(message.entity);
-                if (index !== -1) {
-                    otherPlayersPlayingCounts[index] += 1;
-                } else {
-                    otherPlayersPlaying.push(message.entity);
-                    otherPlayersPlayingCounts.push(1);
+                if (message.playing !== undefined) {
+                    index = otherPlayersPlaying.indexOf(message.entity);
+                    if (index !== -1) {
+                        otherPlayersPlayingCounts[index] += 1;
+                    } else {
+                        otherPlayersPlaying.push(message.entity);
+                        otherPlayersPlayingCounts.push(1);
+                    }
                 }
             }
         }
@@ -270,14 +273,26 @@
 
             playRecording;
 
+        function error(message) {
+            // Send error message to user.
+            Messages.sendMessage(HIFI_RECORDER_CHANNEL, JSON.stringify({
+                command: RECORDER_COMMAND_ERROR,
+                message: message
+            }));
+        }
+
         function play(recording, position, orientation) {
+            var errorMessage;
+
             if (Entity.create(recording, position, orientation)) {
-                log("Play recording " + recordingFilename);
+                log("Play recording " + recording);
                 isPlayingRecording = true;
                 recordingFilename = recording;
                 playRecording(recordingFilename, position, orientation);
             } else {
-                log("Could not play recording " + recordingFilename);
+                errorMessage = "Could not play recording " + recording.slice(4);  // Remove leading "atp:".
+                log(errorMessage);
+                error(errorMessage);
             }
         }
 
@@ -299,6 +314,8 @@
 
         playRecording = function (recording, position, orientation) {
             Recording.loadRecording(recording, function (success) {
+                var errorMessage;
+
                 if (success) {
                     Users.disableIgnoreRadius();
 
@@ -321,7 +338,9 @@
 
                     UserActivityLogger.logAction("playRecordingAC_play_recording");
                 } else {
-                    log("Failed to load recording " + recording);
+                    errorMessage = "Could not load recording " + recording.slice(4);  // Remove leading "atp:".
+                    log(errorMessage);
+                    error(errorMessage);
                     autoPlayTimer = Script.setTimeout(autoPlay, AUTOPLAY_ERROR_INTERVAL);  // Try again later.
                 }
             });
diff --git a/scripts/system/record.js b/scripts/system/record.js
index f9936942fb..1c36a122b9 100644
--- a/scripts/system/record.js
+++ b/scripts/system/record.js
@@ -269,6 +269,7 @@
 
     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",
@@ -350,15 +351,19 @@
 
             message = JSON.parse(message);
 
-            index = playerIDs.indexOf(sender);
-            if (index === -1) {
-                index = playerIDs.length;
-                playerIDs[index] = sender;
+            if (message.command === RECORDER_COMMAND_ERROR) {
+                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);
             }
-            playerIsPlayings[index] = message.playing;
-            playerRecordings[index] = message.recording;
-            playerTimestamps[index] = Date.now();
-            Dialog.updatePlayerDetails(playerIsPlayings, playerRecordings, playerIDs);
         }
 
         function reset() {

From 2bed69cfea194ec1315861b458ad7f9d20518730 Mon Sep 17 00:00:00 2001
From: David Rowe <david@ctrlaltstudio.com>
Date: Fri, 12 May 2017 15:18:25 +1200
Subject: [PATCH 09/15] Display playback errors only for user that initiated
 playback

---
 scripts/system/playRecordingAC.js | 11 ++++++++---
 scripts/system/record.js          |  4 +++-
 2 files changed, 11 insertions(+), 4 deletions(-)

diff --git a/scripts/system/playRecordingAC.js b/scripts/system/playRecordingAC.js
index 5dab0c2ba0..4cf65c7942 100644
--- a/scripts/system/playRecordingAC.js
+++ b/scripts/system/playRecordingAC.js
@@ -267,7 +267,8 @@
 
     Player = (function () {
         // Recording playback functions.
-        var isPlayingRecording = false,
+        var userID = null,
+            isPlayingRecording = false,
             recordingFilename = "",
             autoPlayTimer = null,
 
@@ -277,13 +278,16 @@
             // Send error message to user.
             Messages.sendMessage(HIFI_RECORDER_CHANNEL, JSON.stringify({
                 command: RECORDER_COMMAND_ERROR,
+                user: userID,
                 message: message
             }));
         }
 
-        function play(recording, position, orientation) {
+        function play(user, recording, position, orientation) {
             var errorMessage;
 
+            userID = user;
+
             if (Entity.create(recording, position, orientation)) {
                 log("Play recording " + recording);
                 isPlayingRecording = true;
@@ -305,6 +309,7 @@
                 recording = Entity.find();
                 if (recording) {
                     log("Play persisted recording " + recordingFilename);
+                    userID = null;
                     playRecording(recording.recording, recording.position, recording.orientation);
                 } else {
                     autoPlayTimer = Script.setTimeout(autoPlay, AUTOPLAY_SEARCH_INTERVAL);  // Try again soon.
@@ -424,7 +429,7 @@
             switch (message.command) {
             case PLAYER_COMMAND_PLAY:
                 if (!Player.isPlaying()) {
-                    Player.play(message.recording, message.position, message.orientation);
+                    Player.play(sender, message.recording, message.position, message.orientation);
                 } else {
                     log("Didn't start playing " + message.recording + " because already playing " + Player.recording());
                 }
diff --git a/scripts/system/record.js b/scripts/system/record.js
index 1c36a122b9..d5195d19cc 100644
--- a/scripts/system/record.js
+++ b/scripts/system/record.js
@@ -352,7 +352,9 @@
             message = JSON.parse(message);
 
             if (message.command === RECORDER_COMMAND_ERROR) {
-                error(message.message);
+                if (message.user === MyAvatar.sessionUUID) {
+                    error(message.message);
+                }
             } else {
                 index = playerIDs.indexOf(sender);
                 if (index === -1) {

From 5c5eb298f5c50f4adb586e22ea3b31907242e00c Mon Sep 17 00:00:00 2001
From: David Rowe <david@ctrlaltstudio.com>
Date: Tue, 16 May 2017 09:20:58 +1200
Subject: [PATCH 10/15] Better error message

---
 scripts/system/playRecordingAC.js | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/scripts/system/playRecordingAC.js b/scripts/system/playRecordingAC.js
index 4cf65c7942..c1c70587c6 100644
--- a/scripts/system/playRecordingAC.js
+++ b/scripts/system/playRecordingAC.js
@@ -294,7 +294,7 @@
                 recordingFilename = recording;
                 playRecording(recordingFilename, position, orientation);
             } else {
-                errorMessage = "Could not play recording " + recording.slice(4);  // Remove leading "atp:".
+                errorMessage = "Could not persist recording " + recording.slice(4);  // Remove leading "atp:".
                 log(errorMessage);
                 error(errorMessage);
             }

From 6bdd498ecfca598f73fbb2c0a23e331eabf5940e Mon Sep 17 00:00:00 2001
From: David Rowe <david@ctrlaltstudio.com>
Date: Tue, 16 May 2017 10:47:52 +1200
Subject: [PATCH 11/15] Cancel autoplay when manually playing

---
 scripts/system/playRecordingAC.js | 7 +++++++
 1 file changed, 7 insertions(+)

diff --git a/scripts/system/playRecordingAC.js b/scripts/system/playRecordingAC.js
index c1c70587c6..f62451154b 100644
--- a/scripts/system/playRecordingAC.js
+++ b/scripts/system/playRecordingAC.js
@@ -286,6 +286,11 @@
         function play(user, recording, position, orientation) {
             var errorMessage;
 
+            if (autoPlayTimer) {  // Cancel autoplay.
+                Script.clearTimeout(autoPlayTimer);
+                autoPlayTimer = null;
+            }
+
             userID = user;
 
             if (Entity.create(recording, position, orientation)) {
@@ -297,6 +302,8 @@
                 errorMessage = "Could not persist recording " + recording.slice(4);  // Remove leading "atp:".
                 log(errorMessage);
                 error(errorMessage);
+
+                autoPlayTimer = Script.setTimeout(autoPlay, AUTOPLAY_ERROR_INTERVAL);  // Resume autoplay later.
             }
         }
 

From 6d9bf2b33eb5de7bfd81a313896e7b64610804e4 Mon Sep 17 00:00:00 2001
From: David Rowe <david@ctrlaltstudio.com>
Date: Tue, 16 May 2017 13:58:42 +1200
Subject: [PATCH 12/15] Typos

---
 scripts/system/playRecordingAC.js | 8 ++++----
 1 file changed, 4 insertions(+), 4 deletions(-)

diff --git a/scripts/system/playRecordingAC.js b/scripts/system/playRecordingAC.js
index f62451154b..3d87e9a53f 100644
--- a/scripts/system/playRecordingAC.js
+++ b/scripts/system/playRecordingAC.js
@@ -286,7 +286,7 @@
         function play(user, recording, position, orientation) {
             var errorMessage;
 
-            if (autoPlayTimer) {  // Cancel autoplay.
+            if (autoPlayTimer) {  // Cancel auto-play.
                 Script.clearTimeout(autoPlayTimer);
                 autoPlayTimer = null;
             }
@@ -295,7 +295,7 @@
 
             if (Entity.create(recording, position, orientation)) {
                 log("Play recording " + recording);
-                isPlayingRecording = true;
+                isPlayingRecording = true;  // Immediate feedback.
                 recordingFilename = recording;
                 playRecording(recordingFilename, position, orientation);
             } else {
@@ -303,7 +303,7 @@
                 log(errorMessage);
                 error(errorMessage);
 
-                autoPlayTimer = Script.setTimeout(autoPlay, AUTOPLAY_ERROR_INTERVAL);  // Resume autoplay later.
+                autoPlayTimer = Script.setTimeout(autoPlay, AUTOPLAY_ERROR_INTERVAL);  // Resume auto-play later.
             }
         }
 
@@ -315,7 +315,7 @@
             Script.setTimeout(function () {
                 recording = Entity.find();
                 if (recording) {
-                    log("Play persisted recording " + recordingFilename);
+                    log("Play persisted recording " + recording.recording);
                     userID = null;
                     playRecording(recording.recording, recording.position, recording.orientation);
                 } else {

From 4b175e98e953332d59ea5a279be93f33dd22eb00 Mon Sep 17 00:00:00 2001
From: David Rowe <david@ctrlaltstudio.com>
Date: Tue, 16 May 2017 15:30:30 +1200
Subject: [PATCH 13/15] Delete persistence entity if recording fails to load
 for manual playback

---
 scripts/system/playRecordingAC.js | 38 ++++++++++++++++++++++++-------
 1 file changed, 30 insertions(+), 8 deletions(-)

diff --git a/scripts/system/playRecordingAC.js b/scripts/system/playRecordingAC.js
index 3d87e9a53f..3eb36a2a24 100644
--- a/scripts/system/playRecordingAC.js
+++ b/scripts/system/playRecordingAC.js
@@ -48,9 +48,17 @@
             searchState = SEARCH_IDLE,
             otherPlayersPlaying,
             otherPlayersPlayingCounts,
-            pauseCount;
+            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().
@@ -229,7 +237,7 @@
             return result;
         }
 
-        function destroy() {
+        destroy = function () {
             // Delete current persistence entity.
             if (entityID !== null) {  // Just in case.
                 Entities.deleteEntity(entityID);
@@ -240,6 +248,11 @@
                 Script.clearInterval(updateTimestampTimer);
                 updateTimestampTimer = null;
             }
+        };
+
+        function destroyLater() {
+            // Schedules a call to destroy() when timer threading suits.
+            isDestroyLater = true;
         }
 
         function setUp() {
@@ -260,6 +273,7 @@
             create: create,
             find: find,
             destroy: destroy,
+            destroyLater: destroyLater,
             setUp: setUp,
             tearDown: tearDown
         };
@@ -297,7 +311,7 @@
                 log("Play recording " + recording);
                 isPlayingRecording = true;  // Immediate feedback.
                 recordingFilename = recording;
-                playRecording(recordingFilename, position, orientation);
+                playRecording(recordingFilename, position, orientation, true);
             } else {
                 errorMessage = "Could not persist recording " + recording.slice(4);  // Remove leading "atp:".
                 log(errorMessage);
@@ -317,14 +331,17 @@
                 if (recording) {
                     log("Play persisted recording " + recording.recording);
                     userID = null;
-                    playRecording(recording.recording, recording.position, recording.orientation);
+                    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) {
+        playRecording = function (recording, position, orientation, isManual) {
             Recording.loadRecording(recording, function (success) {
                 var errorMessage;
 
@@ -342,17 +359,22 @@
                     Recording.setPlayerLoop(true);
                     Recording.setPlayerUseSkeletonModel(true);
 
-                    isPlayingRecording = true;
-                    recordingFilename = recording;
-
                     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.
                 }
             });

From 8ab261d1b0a312c2cc151306b812a008aa6a4b5e Mon Sep 17 00:00:00 2001
From: David Rowe <david@ctrlaltstudio.com>
Date: Tue, 16 May 2017 15:52:34 +1200
Subject: [PATCH 14/15] Work around Script.clearTimeout() occasionally failing

---
 scripts/system/playRecordingAC.js | 7 +++++++
 1 file changed, 7 insertions(+)

diff --git a/scripts/system/playRecordingAC.js b/scripts/system/playRecordingAC.js
index 3eb36a2a24..d1b931bc41 100644
--- a/scripts/system/playRecordingAC.js
+++ b/scripts/system/playRecordingAC.js
@@ -301,6 +301,8 @@
             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;
             }
@@ -327,6 +329,11 @@
 
             // 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);

From 0cd5fe35da64b1f83442864e580cac48c8069f71 Mon Sep 17 00:00:00 2001
From: David Rowe <david@ctrlaltstudio.com>
Date: Tue, 16 May 2017 15:54:55 +1200
Subject: [PATCH 15/15] Lint

---
 scripts/system/playRecordingAC.js | 5 +++--
 1 file changed, 3 insertions(+), 2 deletions(-)

diff --git a/scripts/system/playRecordingAC.js b/scripts/system/playRecordingAC.js
index d1b931bc41..98e5220c31 100644
--- a/scripts/system/playRecordingAC.js
+++ b/scripts/system/playRecordingAC.js
@@ -286,6 +286,7 @@
             recordingFilename = "",
             autoPlayTimer = null,
 
+            autoPlay,
             playRecording;
 
         function error(message) {
@@ -323,7 +324,7 @@
             }
         }
 
-        function autoPlay() {
+        autoPlay = function () {
             var recording,
                 AUTOPLAY_SEARCH_DELTA = 1000;
 
@@ -346,7 +347,7 @@
                     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) {