diff --git a/source/RobotAPI/gui-plugins/ArViz/ArVizWidgetController.cpp b/source/RobotAPI/gui-plugins/ArViz/ArVizWidgetController.cpp
index 88dc0f43489bd39214891d67e0cd7504248fdb9c..f0aac485f69cbc8ee8b71b9c446886d510c34220 100644
--- a/source/RobotAPI/gui-plugins/ArViz/ArVizWidgetController.cpp
+++ b/source/RobotAPI/gui-plugins/ArViz/ArVizWidgetController.cpp
@@ -53,6 +53,9 @@ ArVizWidgetController::ArVizWidgetController()
     connect(widget.replayRevisionSpinBox, QOverload<int>::of(&QSpinBox::valueChanged), this, &ArVizWidgetController::onReplaySpinChanged);
     connect(widget.replayRevisionSlider, QOverload<int>::of(&QSlider::valueChanged), this, &ArVizWidgetController::onReplaySliderChanged);
 
+    connect(widget.replayStartButton, &QPushButton::clicked, this, &ArVizWidgetController::onReplayStart);
+    connect(widget.replayStopButton, &QPushButton::clicked, this, &ArVizWidgetController::onReplayStop);
+
 
     // We need a callback from the visualizer, when the layers have changed
     // So we can update the tree accordingly
@@ -458,8 +461,27 @@ void ArVizWidgetController::selectRecording(const viz::Recording& recording)
 
 }
 
+void ArVizWidgetController::onReplayStart(bool)
+{
+    visualizer.stop();
+    replayStarted = true;
+
+    onReplaySliderChanged(widget.replayRevisionSlider->value());
+}
+
+void ArVizWidgetController::onReplayStop(bool)
+{
+    replayStarted = false;
+    visualizer.startAsync(storage);
+}
+
 long ArVizWidgetController::replayToRevision(long revision)
 {
+    if (!replayStarted)
+    {
+        return -1;
+    }
+
     viz::RecordingBatchHeader* matchingBatchHeader = nullptr;
     for (auto& batchHeader : currentRecording.batchHeaders)
     {
@@ -493,8 +515,21 @@ long ArVizWidgetController::replayToRevision(long revision)
     auto updateBegin = std::lower_bound(batch.updates.begin(), batch.updates.end(), pivot, revisionLess);
     auto updateEnd = std::upper_bound(updateBegin, batch.updates.end(), pivot, revisionLess);
 
-    // TODO: Set current state to batch initial state
-    // Apply updates until updateEnd
+    // TODO: Optimize: Only start from the last update position
+    std::map<std::string, viz::LayerUpdate const*> updates;
+
+    for (auto& update : batch.initialState)
+    {
+        updates[update.update.name] = &update.update;
+    }
+    for (auto updateIter = batch.updates.begin(); updateIter != updateEnd; ++updateIter)
+    {
+        updates[updateIter->update.name] = &updateIter->update;
+    }
+    for (auto& pair : updates)
+    {
+        visualizer.apply(*pair.second);
+    }
 
     return updateBegin->timestampInMicroseconds;
 }
diff --git a/source/RobotAPI/gui-plugins/ArViz/ArVizWidgetController.h b/source/RobotAPI/gui-plugins/ArViz/ArVizWidgetController.h
index 822104b64b44ca2278f9192e08aaf4830a943553..01beacf3864b65de94cbad0292b369155fb4ec60 100644
--- a/source/RobotAPI/gui-plugins/ArViz/ArVizWidgetController.h
+++ b/source/RobotAPI/gui-plugins/ArViz/ArVizWidgetController.h
@@ -100,7 +100,6 @@ namespace armarx
 
     public slots:
         /* QT slot declarations */
-        void layerTreeChanged(QTreeWidgetItem* item, int column);
 
     signals:
         /* QT signal declarations */
@@ -108,6 +107,8 @@ namespace armarx
     private:
         void layersChanged(std::vector<viz::CoinLayerID> const& layers);
 
+        void layerTreeChanged(QTreeWidgetItem* item, int column);
+
         void onCollapseAll(bool);
         void onExpandAll(bool);
 
@@ -132,6 +133,8 @@ namespace armarx
 
         void selectRecording(viz::Recording const& recording);
 
+        void onReplayStart(bool);
+        void onReplayStop(bool);
         long replayToRevision(long revision);
 
     private:
@@ -161,6 +164,8 @@ namespace armarx
 
         std::size_t recordingBatchCacheMaxSize = 3;
         std::map<long, TimestampedRecordingBatch> recordingBatchCache;
+
+        bool replayStarted = false;
     };
 }