   [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

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

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