diff --git a/static/style.css b/static/style.css
index d755cc479b4aef218235117a20eeb0c647304000..c0edc32ff2e525e763e19c37e0741a6b55c8223a 100644
--- a/static/style.css
+++ b/static/style.css
@@ -245,6 +245,13 @@ header h1 a:not(:first-of-type) {
     margin-top: 24%;
 }
 
+body.inactive #stream{
+    background-color: black;
+    border: 1em solid black;
+    box-sizing: border-box;
+    min-height: 26em;
+}
+
 @keyframes slideInFromLeft {
   0% {
     transform: translateX(-100%);
diff --git a/static/sync-stream.js b/static/sync-stream.js
index 96e4fbce5a42bee19f3d3092ef1ba9d92f26fb0f..9e989d3c412cad04033114a5091b32f0545c38c9 100644
--- a/static/sync-stream.js
+++ b/static/sync-stream.js
@@ -1,4 +1,6 @@
 var socket = io();
+let hasEverRun = false;
+let player = null;
 
 // Extract foobar from the .stream-foobar key of an element
 function extractStreamKey(e) {
@@ -20,10 +22,23 @@ function hasStream(streamlist, k) {
   return streamlist.some(({ key }) => key === k);
 }
 
+// Returns the Stream from the streamlist
+function getStream(streamlist, k) {
+  return streamlist.find(({ key }) => key === k);
+}
+
 // Send a message to the server when the socket is established
 socket.on('connect', function() {
     let key = getStreamKey()
     socket.emit('join', {"key" : key});
+    // Ask about the state of the stream
+    socket.emit('stream_info', {"key" : key});
+});
+
+// After initial connect, receive a streamlist
+socket.on('stream_info', function(data) {
+    var stream = JSON.parse(data)
+    updateStream(stream, "update");
 });
 
 // Send a message to the server when the socket is established
@@ -50,16 +65,21 @@ socket.on('stream_added', function(data) {
     var streamlist = JSON.parse(data["list"])
     let streamkey = getStreamKey();
     if (hasStream(streamlist, streamkey)) {
-        updateStream("activate", streamkey);
+        let stream = getStream(streamlist, streamkey);
+        updateStream(stream, "added");
+    }else{
+        console.log("..but "+streamkey+" was not in streamlist");
+        console.log(streamlist);
     }
 });
 
 // New streamlist arrives here when webserver gets notivied of a stream removal
 socket.on('stream_removed', function(data) {
     console.log('Stream ' + data['key'] + ' removed.');
-    var streamlist = JSON.parse(data["list"])
     let streamkey = getStreamKey();
-    updateStream("deactivate", streamkey);
+    if (streamkey == data['key']) {
+        updateStream(streamkey, "removed");
+    }
 });
 
 
@@ -76,35 +96,227 @@ function updateViewCount(viewercount) {
 }
 
 // Update the stream when it has been added
-function updateStream(what, streamkey) {
+function updateStream(stream, what) {
+    if (what == "update") {
+        console.log("Stream "+stream.key+" updated")
+        if (!stream.active) {
+            deactivateStream(stream.key);
+        }else{
+            hasEverRun = true;
+        }
+    } else if (what == "added") {
+        console.log("Stream "+stream.key+" has started");
+        hasEverRun = true;
+        activateStream(stream);
+    }else if (what == "removed") {
+        console.log("Stream "+stream+" has stopped");
+        deactivateStream(stream);
+    }
+}
+
+
+function deactivateStream(streamkey) {
+    // Add inactive class to body
+    document.body.classList.add("inactive");
+
+    // Pause the player
+    if (player !== null) {
+        player.pause();
+    }
 
-    if (what == "activate") {
-        console.log("Stream "+streamkey+" has started");
-    }else if (what == "deactivate") {
-        console.log("Stream "+streamkey+" has stopped");
+    // Get width and height from player
+    var videoPlayer = document.getElementById("stream");
+    var w = videoPlayer.offsetWidth;
+    var h = videoPlayer.offsetHeight;
+
+    // Remove video player
+    if (document.getElementById("stream") !== null) { 
+      document.querySelectorAll('#stream').forEach(e => e.remove());
+    }
+
+    if (player !== null) {
+        player.dispose();
+    }
+
+    // Get parent element
+    let content = document.getElementsByTagName("content")[0];
+
+    // Create a div
+    let div = document.createElement("div");
+    div.id = "stream";
+    div.classList.add("stream-"+streamkey)
+    div.style.height = h+"px";
+    div.style.width = w+"px";
+
+    // Add a notice to it
+    let h2 = document.createElement("h2");
+    if (hasEverRun) {
+        h2.textContent = "The stream has ended"
+        h2.classList.add("stopped");
+        document.body.classList.add("stopped");
+    }else{
+        h2.textContent = "The stream hasn't started yet (or it doesn't exist)"
+        h2.classList.add("not_started");
+        document.body.classList.add("not-started");
     }
+    h2.id = "no-stream-notice";
+    div.appendChild(h2);
+
+    content.prepend(div);
+}
+
+function activateStream(stream) {
+    document.body.classList.remove("inactive");
+    document.body.classList.remove("not-started");
+    document.body.classList.remove("stopped");
+
+    // Remove div placeholder
+    if (document.getElementById("stream") !== null) { 
+      document.querySelectorAll('#stream').forEach(e => e.remove());
+    }
+
+    // TODO: Add description, ...
+    addPlayer(stream.key);
+    player = initializePlayer();
+    player.load();
+    player.play();
+
+    updateDescription(stream);
 }
 
 
+function updateDescription(stream) {
+    if (stream.description !== null && stream.description !== "") {
+        let descriptions = document.querySelectorAll('.description');
+        if (descriptions.length === 0) {
+            buildDescriptionBlock();
+        }
+        let description = document.querySelectorAll('.description')[0];
+
+        // TODO: handle markdown...?
+        description.textContent = stream.description;
+    }else{
+        // There was formerly a description which is now gone, so destroy description
+        document.querySelectorAll('.description').forEach(e => e.remove());
+    }
+}
+
+function buildDescriptionBlock() {
+    let content = document.getElementsByTagName("content")[0];
+    let section = document.createElement("section");
+    section.classList.add("description");
+    content.appendChild(section);
+}
+
+
+// When the window is loaded check for errors
 window.onload = function() {
+    if (!document.body.classList.contains("inactive")){
+        player = initializePlayer();
+    }
+    // Run this block with a delay
     setTimeout(function() { 
-        var player = document.getElementById("stream");
-        if (player.classList.contains("vjs-error")) {
-            console.log("Contained Error");
-            var intervalId = setInterval(checkIfStillErrored, 2000);
-            clearInterval(intervalId);
+        var videoPlayer = document.getElementById("stream");
+
+        if (!document.body.classList.contains("inactive")){
+            // If there is an error try every two seconds if the player now finds the
+            // video. Once it is found, remove the interval
+            if (videoPlayer.classList.contains("vjs-error")) {
+                var intervalId = setInterval(function() { 
+                    checkIfStillErrored(intervalId) ;
+                }, 2000);
+            }
+
+            // Autoplay with delay if possible
+            player.play();
+        }else{
+            deactivateStream(getStreamKey());
         }
-    }, 1000);
+
+    }, 200);
 }
 
 
 
-function checkIfStillErrored() {
-    var player = document.getElementById("stream");
-    if (player.classList.contains("vjs-error")) {
-        console.log("Still errored");
-        location.reload(); 
+function checkIfStillErrored(intervalId) {
+    var videoPlayer = document.getElementById("stream");
+    if (videoPlayer.classList.contains("vjs-error")) {
+        console.log("Stream still errored, trying to reload it");
+        player.pause();
+        player.load();
     }else{
-        console.log("Not errored anymore");
+        console.log("Error seems resolved");
+        player.load();
+        player.play();
+        clearInterval(intervalId);
     }
+}
+
+
+function addPlayer(streamkey) {
+    // Get parent element
+    let content = document.getElementsByTagName("content")[0];
+
+    // Create player
+    let videojs = document.createElement("video-js");
+    videojs.id = "stream";
+    videojs.classList.add("vjs-default-skin", "stream-"+streamkey, );
+    videojs.setAttribute("data-setup", '{"fluid": true, "liveui": true}'); 
+    videojs.toggleAttribute('controls'); 
+
+    let source = document.createElement("source");
+    source.src = "../hls/"+streamkey+".m3u8";
+    source.type = "application/x-mpegURL"
+
+    videojs.prepend(source);
+    content.prepend(videojs)
+}
+
+
+function initializePlayer() {
+    var player = videojs('stream');
+    player.autoplay('any');
+
+    function displayMuteifNeeded(player) {
+        if (player.muted()){
+            let title = document.querySelector("#page_title");
+            if (title.querySelector(".muted") == null) {
+                let a = document.createElement("a");
+                let img = document.createElement("img");
+                img.src = "/static/mute.svg";
+                a.classList.add("muted");
+                a.onclick = function() { 
+                    player.muted(false);
+                    displayMuteifNeeded(player);
+                };
+                a.appendChild(img);
+                title.appendChild(a);
+            }
+        }else{
+            let title = document.querySelector("#page_title");
+            title.querySelectorAll('.muted').forEach(e => e.remove());
+        }
+    }
+
+    player.on('play', () => { 
+        displayMuteifNeeded(player);
+    });
+
+    player.on("volumechange",function(){
+        displayMuteifNeeded(player);
+    });
+
+    // player.on('error', () => {
+    //     player.createModal('Retrying connection');
+    //     if (player.error().code === 4) {
+    //         this.player.retryLock = setTimeout(() => {
+    //             player.src({
+    //                 src: data.url
+    //             });
+    //             player.load();
+    //         }, 2000);
+    //     }
+    // });
+
+    return player;
 }
\ No newline at end of file
diff --git a/streamviewer/server.py b/streamviewer/server.py
index 860c5f2d5c9b2f0a334ad1a72fba9396e76b4b2a..6679fc239299371f570c1460680fabecbf9ded16 100644
--- a/streamviewer/server.py
+++ b/streamviewer/server.py
@@ -10,7 +10,7 @@ from flaskext.markdown import Markdown
 from flask_socketio import SocketIO, join_room, leave_room
 
 from .config import initialize_config, APPLICATION_NAME, DEFAULT_CONFIG
-from .streams import Stream, StreamList, value_to_flag
+from .streams import Stream, StreamList, value_to_flag, key_if_not_None
 
 
 # Initialization
@@ -67,17 +67,21 @@ def stream(streamkey):
     # Strip potential trailing slashes
     streamkey = streamkey.rstrip("/")
     stream = streamlist.get_stream(streamkey)
+    streamkey = key_if_not_None(stream, "key", that=streamkey)
+    description = key_if_not_None(stream, "description")
 
     # Render a different Template if the stream is missing
     if stream is None:
+        existed = False
         # Stream was Missing, log warning
-        app.logger.warning("Looking for stream {}, but it didn't exist".format(streamkey))
-        return render_template("stream_missing.html", application_name=APPLICATION_NAME, page_title=config["application"]["page_title"], streamkey=streamkey, list_streams=config["application"]["list_streams"]), 404
+        running_since = None
+        app.logger.info("Client {} looked for non-existent stream {}".format(request.remote_addr, streamkey))
     else:
-        app.logger.debug("Looking for {}/{}.m3u8".format(config["application"]["hls_path"],  streamkey))
+        existed = True
+        app.logger.debug("Client requests stream {} ({}/{}.m3u8)".format(streamkey, config["application"]["hls_path"],  streamkey))
         running_since = humanize.naturaldelta(dt.timedelta(seconds=stream.active_since()))
         # Everything ok, return Stream
-        return render_template('stream.html', application_name=APPLICATION_NAME, page_title=config["application"]["page_title"], hls_path=config["application"]["hls_path"], streamkey=stream.key, description=stream.description, running_since=running_since)
+    return render_template('stream.html', application_name=APPLICATION_NAME, page_title=config["application"]["page_title"], hls_path=config["application"]["hls_path"], streamkey=streamkey, description=description, running_since=running_since, existed=existed)
 
 
 @app.route('/', methods = ['GET'])
@@ -168,17 +172,26 @@ def client_list_connected():
     socketio.emit('stream_list', {'list': json_list})
 
 
-@socketio.on('connect_single')
-def client_single_connected(data):
-    if data is not None:
-        app.logger.info('Client (single page > {}) connected via socket.io'.format(data))
-
 @socketio.on('stream_list')
 def send_streamlist():
     json_list = streamlist.json_list()
     socketio.emit('stream_list', {'list': json_list})
 
 
+@socketio.on('stream_info')
+def send_streaminfo(data):
+    if type(data) is dict and "key" in data.keys():
+        app.logger.info('Client wants info about stream {}'.format(data['key']))
+        key = data["key"]
+        stream = streamlist.get_stream(key)
+        if stream is not None:
+            json = stream.to_json()
+            app.logger.debug('Sending Stream info\n{}'.format(json))
+            socketio.emit('stream_info', json)
+        else:
+            app.logger.warning('Client {} asked for info on non-existing stream {}'.format(request.remote_addr, data['key']))
+
+
 @socketio.on('join')
 def on_join(data):
     app.logger.info('Client connected to stream {}'.format(data['key']))
@@ -187,6 +200,7 @@ def on_join(data):
     count = streamlist.add_viewer(key)
     socketio.emit('viewercount', {'count': count, 'direction': 'up'}, room=key)
 
+
 @socketio.on('leave')
 def on_leave(data):
     app.logger.info('Client left to stream {}'.format(data['key']))
@@ -195,5 +209,6 @@ def on_leave(data):
     count = streamlist.remove_viewer(key)
     socketio.emit('viewercount', {'count': count, 'direction': 'down'}, room=key)
 
+
 if __name__ == '__main__':
     socketio.run(app)
\ No newline at end of file
diff --git a/streamviewer/streams.py b/streamviewer/streams.py
index af1ddaacecacad98a19b0bd99abe637ead4cbe87..51c6a8c71b4905e6e416b8554d6f1a41ff16e71c 100644
--- a/streamviewer/streams.py
+++ b/streamviewer/streams.py
@@ -1,6 +1,7 @@
 #!/usr/bin/env python 
 #-*- coding: utf-8 -*-
 import json
+import copy
 from typing import Optional, NewType, List, Any
 import datetime as dt
 
@@ -38,6 +39,25 @@ def none_if_no_key_value_otherwise(d: dict, key: str, that=None) -> Optional[Any
     return d[key]
 
 
+def key_if_not_None(o, key: str, that=None):
+    """
+    Return the object.key or object["key"] if it exists,
+    otherwise return that (per default None)
+    """
+    if o is None:
+        return that
+    elif type(o) is dict:
+        if key in o.keys():
+            return o[key]
+        else:
+            return that
+    else:
+        try:
+            return getattr(o, key)
+        except TypeError:
+            return that
+
+
 def value_to_flag(value) -> bool:
     """
     Return False if the value was None, otherwise return wether it was in the list
@@ -94,8 +114,19 @@ class Stream():
             return "{} ({})".format(self.key, ", ".join(attributes))
         return "{}".format(self.key)
 
+    def __iter__(self):
+        clone = copy.deepcopy(self)
+        del clone.protected
+        del clone.password
+        del clone.unlisted
+        for key in clone.__dict__:
+            yield key, getattr(clone, key)
+
+    def to_dict(self) -> dict:
+        return dict(self)
+
     def to_json(self):
-        return json.dumps(self, default=jsonconverter, 
+        return json.dumps(self.to_dict(), default=jsonconverter, 
             sort_keys=True, indent=4)
 
     @property
@@ -480,10 +511,12 @@ class StreamList():
         return self
 
 
-
-
 def jsonconverter(o):
-    if isinstance(o, dt.datetime):
+    o = copy.deepcopy(o)
+    if isinstance(o, Stream):
+        # We use to_dict() here, to keep fields like password private : )
+        return o.to_dict()
+    elif isinstance(o, dt.datetime):
         return o.__str__()
     else:
         return o.__dict__
\ No newline at end of file
diff --git a/templates/stream.html b/templates/stream.html
index 7d34234ecc13f1764a4d6d446f64af48a74d9e22..afaea2f328bcfd16c8c598bfa89c48d8026ee645 100644
--- a/templates/stream.html
+++ b/templates/stream.html
@@ -14,52 +14,30 @@
 {% endblock %}
 
 {% block content %}
-  <video-js id="stream" class="vjs-default-skin stream-{{ streamkey }}" data-setup='{"fluid": true, "liveui": true}' controls>
-      <source src="../hls/{{ streamkey }}.m3u8" type="application/x-mpegURL">
-  </video-js>
-  {% if description %}
-    <section class="description">
-        {{ description|markdown }}
-    </section>
+  {% if existed %}
+    <video-js id="stream" class="vjs-default-skin stream-{{ streamkey }}" data-setup='{"fluid": true, "liveui": true}' controls>
+        <source src="../hls/{{ streamkey }}.m3u8" type="application/x-mpegURL">
+    </video-js>
+    {% if description %}
+      <section class="description">
+          {{ description|markdown }}
+      </section>
+    {% endif %}
+  {% else %}
+    <div id="stream" class="stream-{{ streamkey }}">
+      <h2 class="stopped" id="no-stream-notice">The stream hasn't started yet (or it doesn't exist)</h2>
+    </div>
   {% endif %}
 {% endblock %}
 
 {% block footer %}
   {{ super() }}
-    <script src="{{ url_for('static', filename='video.min.js') }}"></script>
-    <script src="{{ url_for('static', filename='videojs-http-streaming.min.js') }}"></script>
+    {% if not existed %}
     <script>
-      let player = videojs('stream');
-      player.autoplay('any');
-
-      function displayMuteifNeeded(player) {
-        if (player.muted()){
-          let title = document.querySelector("#page_title");
-          if (title.querySelector(".muted") == null) {
-            let a = document.createElement("a");
-            let img = document.createElement("img");
-            img.src = "{{ url_for('static', filename='mute.svg') }}";
-            a.classList.add("muted");
-            a.onclick = function() { 
-              player.muted(false);
-              displayMuteifNeeded(player);
-            };
-            a.appendChild(img);
-            title.appendChild(a);
-          }
-        }else{
-          let title = document.querySelector("#page_title");
-          title.querySelectorAll('.muted').forEach(e => e.remove());
-        }
-      }
-
-      player.on('play', () => { 
-        displayMuteifNeeded(player);
-      });
-
-      player.on("volumechange",function(){
-        displayMuteifNeeded(player);
-      });
+      document.body.classList.add("inactive");
     </script>
+    {% endif %}
+    <script src="{{ url_for('static', filename='video.min.js') }}"></script>
+    <script src="{{ url_for('static', filename='videojs-http-streaming.min.js') }}"></script>
     <script src="{{ url_for('static', filename='sync-stream.js') }}"></script>
 {% endblock %}
diff --git a/templates/stream_missing.html b/templates/stream_missing.html
deleted file mode 100644
index 6ad08e4a13aee4482b91cf93266b024aced95d8b..0000000000000000000000000000000000000000
--- a/templates/stream_missing.html
+++ /dev/null
@@ -1,25 +0,0 @@
-{% extends "layout.html" %}
-{% block title %}{{ page_title }}/{{ streamkey }}{% endblock %}
-{% block head %}
-  <link href="{{ url_for('static', filename='video-js.css') }}" rel="stylesheet">
-  {{ super() }}
-{% endblock %}
-
-{% block header %}
-  <h1 id="page_title"><a href="/">{{ page_title }}</a>/<s>{{ streamkey }}</s></h1>
-{% endblock %}
-
-{% block content %}
-    <h2 class="404">Uh-oh!</h2>
-    <h2>The stream {{ streamkey }} doesn't seem to be here (anymore?)</h2>
-    {% if list_streams %}
-        <p>Have a look at the <a href="/streams">list of current streams</a></p>
-    {% endif %}
-{% endblock %}
-
-{% block footer %}
-  {{ super() }}
-{% endblock %}
-
-
-