diff --git a/lib/PiTube/Controller/Record.pm b/lib/PiTube/Controller/Record.pm
new file mode 100644
index 0000000000000000000000000000000000000000..67be99c4aafd842d950d817f36c18ad9289bcf5d
--- /dev/null
+++ b/lib/PiTube/Controller/Record.pm
@@ -0,0 +1,61 @@
+package PiTube::Controller::Record;
+use Mojo::Base 'Mojolicious::Controller';
+
+sub list {
+    my $c = shift;
+
+    my $cond = {
+        is_active => 't'
+    };
+
+    my $records = $c->schema->resultset('Record_view')->search(
+        $cond,
+        { order_by => { -desc => 'begin' }}
+    );
+
+    my @records = ();
+
+    RECORD:
+    while ( my $record = $records->next()) {
+
+        my %record = (
+            $record->get_columns(),
+        );
+
+        push @records, \%record;
+    }
+
+    $c->render( json => \@records );
+}
+
+sub player {
+    my $c = shift;
+
+    # vzdy aktualizovat
+    $c->session->{user}{acl} = $c->schema->resultset('ACL')->user_acl(
+        $c->session->{user}
+    );
+
+    # record
+    my $record = $c->schema->resultset('Record_view')->find({
+        id => $c->stash->{id}
+    });
+
+    if ( ! $record ) {
+        $c->render('record/404');
+        return;
+    }
+
+    $c->stash->{record} = {
+        $record->get_columns,
+    };
+
+    if ( ! ( $c->session->{user}{acl}{ $record->stream_key } & 4 ) ) { # TODO:constant
+        $c->render('record/403');
+        return;
+    }
+
+    $c->render();
+}
+
+1;
diff --git a/lib/PiTube/Schema/Result/Record.pm b/lib/PiTube/Schema/Result/Record.pm
new file mode 100644
index 0000000000000000000000000000000000000000..d19054fdc477b7922f208ebb14368d194f23d6fa
--- /dev/null
+++ b/lib/PiTube/Schema/Result/Record.pm
@@ -0,0 +1,43 @@
+package PiTube::Schema::Result::Record;
+
+use strict;
+use warnings;
+
+use base 'DBIx::Class::Core';
+
+our $VERSION = 1;
+
+__PACKAGE__->table('records');
+
+__PACKAGE__->add_columns(
+    id => {
+        data_type         => 'integer',
+        is_auto_increment => 1,
+        is_nullable       => 0,
+        sequence          => 'uid_seq'
+    },
+    qw(
+        begin
+        end
+        is_active
+        is_protected
+        stream_id
+        publish_user_id
+        recorder
+        path
+        description
+        info
+    ),
+);
+
+__PACKAGE__->belongs_to(
+    stream => 'PiTube::Schema::Result::Stream',
+    {
+        'foreign.id' => 'self.stream_id',
+    },
+);
+__PACKAGE__->set_primary_key('id');
+
+
+1;
+
diff --git a/lib/PiTube/Schema/Result/Record_view.pm b/lib/PiTube/Schema/Result/Record_view.pm
new file mode 100644
index 0000000000000000000000000000000000000000..cc2ac34043302daf4497030f3356c86ee6981f03
--- /dev/null
+++ b/lib/PiTube/Schema/Result/Record_view.pm
@@ -0,0 +1,19 @@
+package PiTube::Schema::Result::Record_view;
+
+use strict;
+use warnings;
+
+use base 'PiTube::Schema::Result::Record';
+
+our $VERSION = 1;
+
+__PACKAGE__->table('records_view');
+
+__PACKAGE__->add_columns(qw(
+   stream_key
+   stream_name
+   stream_is_public
+   publish_user_name
+));
+
+1;
diff --git a/sql/migrations.sql b/sql/migrations.sql
index 6868eece95bbc61b9b89c7e9daf5a6c0a426ccd8..8a4887c2e6ff1fdd544042872ebb9e9880c27aaa 100644
--- a/sql/migrations.sql
+++ b/sql/migrations.sql
@@ -63,3 +63,34 @@ select "streams".*,
 from "streams"
 left join "users" on ("streams"."publish_user_id" = "users"."id")
 ;
+
+-- 4 up
+create table "records" (
+    "id" integer not null default nextval('uid_seq'),
+    "begin" timestamp(0) not null default now(),
+    "end" timestamp(0),
+    "is_active" bool not null default true,
+    "is_protected" bool not null default false,
+    "stream_id" integer not null,
+    "publish_user_id" integer not null,
+    "recorder" text,
+    "path" text,
+    "description" text,
+    "info" text,
+    primary key("id"),
+    foreign key ("stream_id") references "streams" ("id") on update cascade on delete restrict,
+    foreign key ("publish_user_id") references "users" ("id") on update cascade on delete restrict
+);
+
+-- 5 up
+
+create view "records_view" as
+select "records".*,
+    "streams"."key" as "stream_key",
+    "streams"."name" as "stream_name",
+    "streams"."is_public" as "stream_is_public",
+    "users"."name" as "publish_user_name"
+from "records"
+left join "users" on ("records"."publish_user_id" = "users"."id")
+join "streams" on ("records"."stream_id" = "streams"."id")
+;
diff --git a/templates/archive.html.ep b/templates/archive.html.ep
new file mode 100644
index 0000000000000000000000000000000000000000..d06badfc10306a1fb32fa798825fbb8fa38c012c
--- /dev/null
+++ b/templates/archive.html.ep
@@ -0,0 +1,35 @@
+% layout 'default';
+% title 'Archiv vysílání';
+
+<div class="container pt-2 lg:pb-2 ">
+<table id="Records"
+    class="table table--bordered"
+    data-row-style="rowStyle"
+    data-url="/api/records">
+  <thead>
+    <tr>
+      <th data-field="stream_name" data-width="50" data-width-unit="%">Stream</th>
+      <th data-field="publish_user_name" data-width="30" data-width-unit="%">Vysílal</th>
+      <th data-field="begin" data-width="20" data-width-unit="%">Datum vysílání</th>
+    </tr>
+  </thead>
+</table>
+</div>
+
+<script>
+$('#Records').bootstrapTable({
+    onClickCell: function (field, value, row, $element) {
+        window.location.href ='/archive/' + row.id;
+    },
+});
+
+function rowStyle(row, index) {
+    return {
+        css: {
+            cursor: 'pointer'
+        }
+    }
+}
+
+</script>
+
diff --git a/templates/record/player.html.ep b/templates/record/player.html.ep
new file mode 100644
index 0000000000000000000000000000000000000000..46ca6e571bca9f72ffbb471d7fafb5ce250381f2
--- /dev/null
+++ b/templates/record/player.html.ep
@@ -0,0 +1,3 @@
+% layout 'default';
+% title $c->stash->{record}{stream_name} . ' - ' . $c->stash->{record}{begin};
+%= include 'includes/player', src => $c->stash->{record}{path}, live => 0;