   [frames-ch]
   (priority-chan [:frame frames-ch] [:blink make-cursor-blink-chan]))
 
+(defn pipe
+  "Copies elements received on input-ch to output-ch until stop-ch closes."
+  [input-ch stop-ch]
+  (let [output-ch (chan (sliding-buffer 1))]
+    (go-loop []
+      (let [[v c] (alts! [input-ch stop-ch])]
+        (condp = c
+          input-ch (if v
+                     (do
+                       (>! output-ch v)
+                       (recur))
+                     (close! output-ch))
+          stop-ch nil)))
+    output-ch))
+
 (defmulti start-playback
   "The heart of the player. Coordinates dispatching of state update events like
   terminal line updating, time reporting and cursor blinking. Returns player

  for each element. It tries to always stay 'on the schedule' by measuring
  elapsed time and skipping elements if necessary."
  [coll]
  (let [ch (chan (sliding-buffer 1))
        start (js/Date.)]
    (go
      (loop [coll coll

        speed (:speed player)
        all-frames (-> player :source :frames)
        frames (-> all-frames (next-frames start-at) (frames-at-speed speed))
        stop-playback-ch (chan)
        frames-ch (pipe (coll->chan frames) stop-playback-ch)
        events-ch (screen-events frames-ch)
        elapsed-time #(* (elapsed-time-since start) speed)
        time-events (repeatedly (fn [] [0.3 (+ start-at (elapsed-time))]))
        timer-ch (pipe (coll->chan time-events) stop-playback-ch)
        stop-fn (fn []
                  (close! stop-playback-ch)
                  (elapsed-time))]
