diff --git a/.gitignore b/.gitignore
index c1b8300f2989fd0c22b266df093efcd1354367df..66ec5a41dbee681dbbbbe0e61802282c0e629e8e 100644
--- a/.gitignore
+++ b/.gitignore
@@ -72,7 +72,7 @@ api/music
 api/media
 api/staticfiles
 api/static
-
+api/.pytest_cache
 
 # Front
 front/node_modules/
diff --git a/CHANGELOG b/CHANGELOG
index 03665014f01d21304efe339f5442290dcf3b6d3a..07d1dedbd2ba7450295491d6ce4d5cfaf1ccfb01 100644
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -6,6 +6,20 @@ Changelog
 ----------------
 
 - Front: Now reset player colors when track has no cover (#46)
+- Front: play button now disabled for unplayable tracks
+
+Transcoding:
+
+Basic transcoding is now available to/from the following formats : ogg and mp3.
+
+*This is still an alpha feature at the moment, please report any bug.*
+
+This relies internally on FFMPEG and can put some load on your server.
+It's definitely recommended you setup some caching for the transcoded files
+at your webserver level. Check the the exemple nginx file at deploy/nginx.conf
+for an implementation.
+
+On the frontend, usage of transcoding should be transparent in the player.
 
 0.4 (2018-02-18)
 ----------------
diff --git a/api/Dockerfile b/api/Dockerfile
index 3281e6f562a64dd7df21d8f2a7186814e3ba985c..5d4e858574a0063b756a168f48b529e904bc427b 100644
--- a/api/Dockerfile
+++ b/api/Dockerfile
@@ -3,7 +3,7 @@ FROM python:3.5
 ENV PYTHONUNBUFFERED 1
 
 # Requirements have to be pulled and installed here, otherwise caching won't work
-
+RUN echo 'deb http://httpredir.debian.org/debian/ jessie-backports main' > /etc/apt/sources.list.d/ffmpeg.list
 COPY ./requirements.apt /requirements.apt
 RUN apt-get update -qq && grep "^[^#;]" requirements.apt | xargs apt-get install -y
 RUN curl -L https://github.com/acoustid/chromaprint/releases/download/v1.4.2/chromaprint-fpcalc-1.4.2-linux-x86_64.tar.gz | tar -xz -C /usr/local/bin --strip 1
diff --git a/api/docker/Dockerfile.test b/api/docker/Dockerfile.test
index 08b437cf25d136579ab04bf50a484cd8bccb8883..069b89c2f83de1628d485374545f908fe52ec93c 100644
--- a/api/docker/Dockerfile.test
+++ b/api/docker/Dockerfile.test
@@ -1,9 +1,10 @@
 FROM python:3.5
 
 ENV PYTHONUNBUFFERED 1
-ENV PYTHONDONTWRITEBYTECODE  1
+ENV PYTHONDONTWRITEBYTECODE 1
 
 # Requirements have to be pulled and installed here, otherwise caching won't work
+RUN echo 'deb http://httpredir.debian.org/debian/ jessie-backports main' > /etc/apt/sources.list.d/ffmpeg.list
 COPY ./requirements.apt /requirements.apt
 COPY ./install_os_dependencies.sh /install_os_dependencies.sh
 RUN bash install_os_dependencies.sh install
diff --git a/api/funkwhale_api/music/forms.py b/api/funkwhale_api/music/forms.py
new file mode 100644
index 0000000000000000000000000000000000000000..04e4bfe057c0723b161265c63000a2494c687a9d
--- /dev/null
+++ b/api/funkwhale_api/music/forms.py
@@ -0,0 +1,23 @@
+from django import forms
+
+from . import models
+
+
+class TranscodeForm(forms.Form):
+    FORMAT_CHOICES = [
+        ('ogg', 'ogg'),
+        ('mp3', 'mp3'),
+    ]
+
+    to = forms.ChoiceField(choices=FORMAT_CHOICES)
+    BITRATE_CHOICES = [
+        (64, '64'),
+        (128, '128'),
+        (256, '256'),
+    ]
+    bitrate = forms.ChoiceField(
+        choices=BITRATE_CHOICES, required=False)
+
+    track_file = forms.ModelChoiceField(
+        queryset=models.TrackFile.objects.all()
+    )
diff --git a/api/funkwhale_api/music/migrations/0018_auto_20180218_1554.py b/api/funkwhale_api/music/migrations/0018_auto_20180218_1554.py
new file mode 100644
index 0000000000000000000000000000000000000000..c45298798b87f52dfe101b1d5f4a10a94407f10b
--- /dev/null
+++ b/api/funkwhale_api/music/migrations/0018_auto_20180218_1554.py
@@ -0,0 +1,28 @@
+# Generated by Django 2.0.2 on 2018-02-18 15:54
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        ('music', '0017_auto_20171227_1728'),
+    ]
+
+    operations = [
+        migrations.AddField(
+            model_name='trackfile',
+            name='mimetype',
+            field=models.CharField(blank=True, max_length=200, null=True),
+        ),
+        migrations.AlterField(
+            model_name='importjob',
+            name='source',
+            field=models.CharField(max_length=500),
+        ),
+        migrations.AlterField(
+            model_name='importjob',
+            name='status',
+            field=models.CharField(choices=[('pending', 'Pending'), ('finished', 'Finished'), ('errored', 'Errored'), ('skipped', 'Skipped')], default='pending', max_length=30),
+        ),
+    ]
diff --git a/api/funkwhale_api/music/migrations/0019_populate_mimetypes.py b/api/funkwhale_api/music/migrations/0019_populate_mimetypes.py
new file mode 100644
index 0000000000000000000000000000000000000000..127aa5e69a245215e2b0f16e0b5e3081efb7db49
--- /dev/null
+++ b/api/funkwhale_api/music/migrations/0019_populate_mimetypes.py
@@ -0,0 +1,34 @@
+# -*- coding: utf-8 -*-
+from __future__ import unicode_literals
+import os
+
+from django.db import migrations, models
+from funkwhale_api.music.utils import guess_mimetype
+
+
+def populate_mimetype(apps, schema_editor):
+    TrackFile = apps.get_model("music", "TrackFile")
+
+    for tf in TrackFile.objects.filter(audio_file__isnull=False, mimetype__isnull=True).only('audio_file'):
+        try:
+            tf.mimetype = guess_mimetype(tf.audio_file)
+        except Exception as e:
+            print('Error on track file {}: {}'.format(tf.pk, e))
+            continue
+        print('Track file {}: {}'.format(tf.pk, tf.mimetype))
+        tf.save(update_fields=['mimetype'])
+
+
+def rewind(apps, schema_editor):
+    pass
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        ('music', '0018_auto_20180218_1554'),
+    ]
+
+    operations = [
+        migrations.RunPython(populate_mimetype, rewind),
+    ]
diff --git a/api/funkwhale_api/music/models.py b/api/funkwhale_api/music/models.py
index f8373ab4d0f286dae34da01dfbffe8967ed9f9a0..3ebd07419e6d0fc35608fd4e63f7c2b990e83657 100644
--- a/api/funkwhale_api/music/models.py
+++ b/api/funkwhale_api/music/models.py
@@ -18,6 +18,7 @@ from versatileimagefield.fields import VersatileImageField
 from funkwhale_api import downloader
 from funkwhale_api import musicbrainz
 from . import importers
+from . import utils
 
 
 class APIModelMixin(models.Model):
@@ -364,6 +365,7 @@ class TrackFile(models.Model):
     source = models.URLField(null=True, blank=True)
     duration = models.IntegerField(null=True, blank=True)
     acoustid_track_id = models.UUIDField(null=True, blank=True)
+    mimetype = models.CharField(null=True, blank=True, max_length=200)
 
     def download_file(self):
         # import the track file, since there is not any
@@ -393,6 +395,10 @@ class TrackFile(models.Model):
             self.track.full_name,
             os.path.splitext(self.audio_file.name)[-1])
 
+    def save(self, **kwargs):
+        if not self.mimetype and self.audio_file:
+            self.mimetype = utils.guess_mimetype(self.audio_file)
+        return super().save(**kwargs)
 
 class ImportBatch(models.Model):
     IMPORT_BATCH_SOURCES = [
diff --git a/api/funkwhale_api/music/serializers.py b/api/funkwhale_api/music/serializers.py
index 506893a4d23ae1026f5f26a159eeb841443dde96..41de30f1026e54e0f98a50926934f23ce2fd0c84 100644
--- a/api/funkwhale_api/music/serializers.py
+++ b/api/funkwhale_api/music/serializers.py
@@ -28,7 +28,14 @@ class TrackFileSerializer(serializers.ModelSerializer):
 
     class Meta:
         model = models.TrackFile
-        fields = ('id', 'path', 'duration', 'source', 'filename', 'track')
+        fields = (
+            'id',
+            'path',
+            'duration',
+            'source',
+            'filename',
+            'mimetype',
+            'track')
 
     def get_path(self, o):
         url = o.path
diff --git a/api/funkwhale_api/music/utils.py b/api/funkwhale_api/music/utils.py
index 32b1aeb470af219124e8a0c533f13b6e991981c5..0e4318e563ee5341e0d8b9d0aa32291eea867e7a 100644
--- a/api/funkwhale_api/music/utils.py
+++ b/api/funkwhale_api/music/utils.py
@@ -1,7 +1,9 @@
+import magic
 import re
 
 from django.db.models import Q
 
+
 def normalize_query(query_string,
                     findterms=re.compile(r'"([^"]+)"|(\S+)').findall,
                     normspace=re.compile(r'\s{2,}').sub):
@@ -15,6 +17,7 @@ def normalize_query(query_string,
     '''
     return [normspace(' ', (t[0] or t[1]).strip()) for t in findterms(query_string)]
 
+
 def get_query(query_string, search_fields):
     ''' Returns a query, that is a combination of Q objects. That combination
         aims to search keywords within a model by testing the given search fields.
@@ -35,3 +38,8 @@ def get_query(query_string, search_fields):
         else:
             query = query & or_query
     return query
+
+
+def guess_mimetype(f):
+    b = min(100000, f.size)
+    return magic.from_buffer(f.read(b), mime=True)
diff --git a/api/funkwhale_api/music/views.py b/api/funkwhale_api/music/views.py
index 2395454c46429a5b48ccee7fe22e25bf39808318..8e46cbd71612f6b6d65563d136408efd4e0401ac 100644
--- a/api/funkwhale_api/music/views.py
+++ b/api/funkwhale_api/music/views.py
@@ -1,11 +1,16 @@
+import ffmpeg
 import os
 import json
+import subprocess
 import unicodedata
 import urllib
+
 from django.urls import reverse
 from django.db import models, transaction
 from django.db.models.functions import Length
 from django.conf import settings
+from django.http import StreamingHttpResponse
+
 from rest_framework import viewsets, views, mixins
 from rest_framework.decorators import detail_route, list_route
 from rest_framework.response import Response
@@ -19,6 +24,7 @@ from funkwhale_api.common.permissions import (
     ConditionalAuthentication, HasModelPermission)
 from taggit.models import Tag
 
+from . import forms
 from . import models
 from . import serializers
 from . import importers
@@ -183,6 +189,40 @@ class TrackFileViewSet(viewsets.ReadOnlyModelViewSet):
             f.audio_file.url)
         return response
 
+    @list_route(methods=['get'])
+    def viewable(self, request, *args, **kwargs):
+        return Response({}, status=200)
+
+    @list_route(methods=['get'])
+    def transcode(self, request, *args, **kwargs):
+        form = forms.TranscodeForm(request.GET)
+        if not form.is_valid():
+            return Response(form.errors, status=400)
+
+        f = form.cleaned_data['track_file']
+        output_kwargs = {
+            'format': form.cleaned_data['to']
+        }
+        args = (ffmpeg
+            .input(f.audio_file.path)
+            .output('pipe:', **output_kwargs)
+            .get_args()
+        )
+        # we use a generator here so the view return immediatly and send
+        # file chunk to the browser, instead of blocking a few seconds
+        def _transcode():
+            p = subprocess.Popen(
+                ['ffmpeg'] + args,
+                stdout=subprocess.PIPE)
+            for line in p.stdout:
+                yield line
+
+        response = StreamingHttpResponse(
+            _transcode(), status=200,
+            content_type=form.cleaned_data['to'])
+
+        return response
+
 
 class TagViewSet(viewsets.ReadOnlyModelViewSet):
     queryset = Tag.objects.all().order_by('name')
diff --git a/api/requirements.apt b/api/requirements.apt
index e28360b5658fe17d9f2a5afa4d6e28feedf87101..462a5a705c75b319636449490c6e3c29b089a7f1 100644
--- a/api/requirements.apt
+++ b/api/requirements.apt
@@ -5,6 +5,7 @@ libjpeg-dev
 zlib1g-dev
 libpq-dev
 postgresql-client
-libav-tools
+libmagic-dev
+ffmpeg
 python3-dev
 curl
diff --git a/api/requirements/base.txt b/api/requirements/base.txt
index f38da9629041fcd8f9f1a0620d00ec17c0823f32..133fcc0cb65f0deb430d73ff5259d05efc215cc2 100644
--- a/api/requirements/base.txt
+++ b/api/requirements/base.txt
@@ -57,3 +57,5 @@ git+https://github.com/EliotBerriot/django-cachalot.git@django-2
 django-dynamic-preferences>=1.5,<1.6
 pyacoustid>=1.1.5,<1.2
 raven>=6.5,<7
+python-magic==0.4.15
+ffmpeg-python==0.1.10
diff --git a/api/tests/music/test_models.py b/api/tests/music/test_models.py
index 165415465d1431d7294f065efccdb390b87b6b96..2eb1f276332fc32bcd01e947fac72c3774cfaea8 100644
--- a/api/tests/music/test_models.py
+++ b/api/tests/music/test_models.py
@@ -1,9 +1,12 @@
+import os
 import pytest
 
 from funkwhale_api.music import models
 from funkwhale_api.music import importers
 from funkwhale_api.music import tasks
 
+DATA_DIR = os.path.dirname(os.path.abspath(__file__))
+
 
 def test_can_store_release_group_id_on_album(factories):
     album = factories['music.Album']()
@@ -48,3 +51,15 @@ def test_import_job_is_bound_to_track_file(factories, mocker):
     tasks.import_job_run(import_job_id=job.pk)
     job.refresh_from_db()
     assert job.track_file.track == track
+
+@pytest.mark.parametrize('extention,mimetype', [
+    ('ogg', 'audio/ogg'),
+    ('mp3', 'audio/mpeg'),
+])
+def test_audio_track_mime_type(extention, mimetype, factories):
+
+    name = '.'.join(['test', extention])
+    path = os.path.join(DATA_DIR, name)
+    tf = factories['music.TrackFile'](audio_file__from_path=path)
+
+    assert tf.mimetype == mimetype
diff --git a/deploy/nginx.conf b/deploy/nginx.conf
index cf865a9ea7b3ef84eeb6eda8d6d65f509790eaf5..dfdbac2ae909bf9d5efafb1698f4fd8256e17eba 100644
--- a/deploy/nginx.conf
+++ b/deploy/nginx.conf
@@ -39,6 +39,15 @@ server {
 
     root /srv/funkwhale/front/dist;
 
+    # global proxy conf
+    proxy_set_header Host $host;
+    proxy_set_header X-Real-IP $remote_addr;
+    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
+    proxy_set_header X-Forwarded-Proto $scheme;
+    proxy_set_header X-Forwarded-Host   $host:$server_port;
+    proxy_set_header X-Forwarded-Port   $server_port;
+    proxy_redirect off;
+
     location / {
         try_files $uri $uri/ @rewrites;
     }
@@ -49,15 +58,9 @@ server {
     location /api/ {
         # this is needed if you have file import via upload enabled
         client_max_body_size 30M;
-        proxy_set_header Host $host;
-        proxy_set_header X-Real-IP $remote_addr;
-        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
-        proxy_set_header X-Forwarded-Proto $scheme;
-        proxy_set_header X-Forwarded-Host   $host:$server_port;
-        proxy_set_header X-Forwarded-Port   $server_port;
-        proxy_redirect off;
         proxy_pass   http://funkwhale-api/api/;
     }
+
     location /media/ {
         alias /srv/funkwhale/data/media/;
     }
@@ -70,6 +73,41 @@ server {
         alias   /srv/funkwhale/data/media;
     }
 
+    # Transcoding logic and caching
+    location = /transcode-auth {
+        # needed so we can authenticate transcode requests, but still
+        # cache the result
+        internal;
+        set $query '';
+        # ensure we actually pass the jwt to the underlytin auth url
+        if ($request_uri ~* "[^\?]+\?(.*)$") {
+            set $query $1;
+        }
+        proxy_set_header X-Forwarded-Host   $host:$server_port;
+        proxy_set_header X-Forwarded-Port   $server_port;
+        proxy_pass http://api:12081/api/v1/trackfiles/viewable/?$query;
+        proxy_pass_request_body off;
+        proxy_set_header        Content-Length "";
+    }
+
+    location /api/v1/trackfiles/transcode/ {
+        # this block deals with authenticating and caching transcoding
+        # requests. Caching is heavily recommended as transcoding
+        # is a CPU intensive process.
+        auth_request /transcode-auth;
+        if ($args ~ (.*)jwt=[^&]*(.*)) {
+            set $cleaned_args $1$2;
+        }
+        proxy_cache_key "$scheme$request_method$host$uri$is_args$cleaned_args";
+        proxy_cache transcode;
+        proxy_cache_valid 200 7d;
+        proxy_ignore_headers "Set-Cookie";
+        proxy_hide_header "Set-Cookie";
+        add_header X-Cache-Status $upstream_cache_status;
+        proxy_pass   http://funkwhale-api;
+    }
+    # end of transcoding logic
+
     location /staticfiles/ {
         # django static files
         alias /srv/funkwhale/data/static/;
diff --git a/docker/nginx/conf.dev b/docker/nginx/conf.dev
index 1b749c30a24006e2e954044bc50ec03a94d1adb6..29c04fc6643c17a8918c9ad4546993d49587336e 100644
--- a/docker/nginx/conf.dev
+++ b/docker/nginx/conf.dev
@@ -26,23 +26,59 @@ http {
     keepalive_timeout  65;
 
     #gzip  on;
+    proxy_cache_path /tmp/funkwhale-transcode levels=1:2 keys_zone=transcode:10m max_size=1g inactive=24h use_temp_path=off;
 
     server {
         listen 6001;
         charset     utf-8;
         client_max_body_size 20M;
+
+        # global proxy pass config
+        proxy_set_header Host $host;
+        proxy_set_header X-Real-IP $remote_addr;
+        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
+        proxy_set_header X-Forwarded-Proto $scheme;
+        proxy_set_header X-Forwarded-Host   localhost:8080;
+        proxy_set_header X-Forwarded-Port   8080;
+        proxy_redirect off;
+
         location /_protected/media {
             internal;
             alias   /protected/media;
         }
-        location / {
-            proxy_set_header Host $host;
-            proxy_set_header X-Real-IP $remote_addr;
-            proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
-            proxy_set_header X-Forwarded-Proto $scheme;
+        location = /transcode-auth {
+            # needed so we can authenticate transcode requests, but still
+            # cache the result
+            internal;
+            set $query '';
+            # ensure we actually pass the jwt to the underlytin auth url
+            if ($request_uri ~* "[^\?]+\?(.*)$") {
+                set $query $1;
+            }
             proxy_set_header X-Forwarded-Host   localhost:8080;
             proxy_set_header X-Forwarded-Port   8080;
-            proxy_redirect off;
+            proxy_pass http://api:12081/api/v1/trackfiles/viewable/?$query;
+            proxy_pass_request_body off;
+            proxy_set_header        Content-Length "";
+        }
+
+        location /api/v1/trackfiles/transcode/ {
+            # this block deals with authenticating and caching transcoding
+            # requests. Caching is heavily recommended as transcoding
+            # is a CPU intensive process.
+            auth_request /transcode-auth;
+            if ($args ~ (.*)jwt=[^&]*(.*)) {
+                set $cleaned_args $1$2;
+            }
+            proxy_cache_key "$scheme$request_method$host$uri$is_args$cleaned_args";
+            proxy_cache transcode;
+            proxy_cache_valid 200 7d;
+            proxy_ignore_headers "Set-Cookie";
+            proxy_hide_header "Set-Cookie";
+            add_header X-Cache-Status $upstream_cache_status;
+            proxy_pass http://api:12081;
+        }
+        location / {
             proxy_pass   http://api:12081/;
         }
     }
diff --git a/front/src/audio/formats.js b/front/src/audio/formats.js
new file mode 100644
index 0000000000000000000000000000000000000000..f6e2157a15d6c64afff10addd30ceba724dcdc65
--- /dev/null
+++ b/front/src/audio/formats.js
@@ -0,0 +1,10 @@
+export default {
+  formats: [
+    // 'audio/ogg',
+    'audio/mpeg'
+  ],
+  formatsMap: {
+    'audio/ogg': 'ogg',
+    'audio/mpeg': 'mp3'
+  }
+}
diff --git a/front/src/components/audio/PlayButton.vue b/front/src/components/audio/PlayButton.vue
index 4767255ecae8b6bb27a872614f9a4e029146ded9..451cdcf0188a9e213e4d7cb785e9dd926ff23147 100644
--- a/front/src/components/audio/PlayButton.vue
+++ b/front/src/components/audio/PlayButton.vue
@@ -1,6 +1,6 @@
 <template>
   <div :class="['ui', {'tiny': discrete}, 'buttons']">
-    <button title="Add to current queue" @click="add" :class="['ui', {'mini': discrete}, 'button']">
+    <button title="Add to current queue" @click="add" :class="['ui', {'mini': discrete}, {disabled: playableTracks.length === 0}, 'button']">
       <i class="ui play icon"></i>
       <template v-if="!discrete"><slot>Play</slot></template>
     </button>
@@ -36,20 +36,25 @@ export default {
       jQuery(this.$el).find('.ui.dropdown').dropdown()
     }
   },
-  methods: {
-    add () {
+  computed: {
+    playableTracks () {
+      let tracks
       if (this.track) {
-        this.$store.dispatch('queue/append', {track: this.track})
+        tracks = [this.track]
       } else {
-        this.$store.dispatch('queue/appendMany', {tracks: this.tracks})
+        tracks = this.tracks
       }
+      return tracks.filter(e => {
+        return e.files.length > 0
+      })
+    }
+  },
+  methods: {
+    add () {
+      this.$store.dispatch('queue/appendMany', {tracks: this.playableTracks})
     },
     addNext (next) {
-      if (this.track) {
-        this.$store.dispatch('queue/append', {track: this.track, index: this.$store.state.queue.currentIndex + 1})
-      } else {
-        this.$store.dispatch('queue/appendMany', {tracks: this.tracks, index: this.$store.state.queue.currentIndex + 1})
-      }
+      this.$store.dispatch('queue/appendMany', {tracks: this.playableTracks, index: this.$store.state.queue.currentIndex + 1})
       if (next) {
         this.$store.dispatch('queue/next')
       }
diff --git a/front/src/components/audio/Track.vue b/front/src/components/audio/Track.vue
index a513c468f6792d2013bb25b6b4946a863be667a7..d8dcaff9b393013be60b2cb7e0b82ac0b6329e31 100644
--- a/front/src/components/audio/Track.vue
+++ b/front/src/components/audio/Track.vue
@@ -1,21 +1,20 @@
 <template>
   <audio
     ref="audio"
-    :src="url"
     @error="errored"
-    @progress="updateLoad"
     @loadeddata="loaded"
+    @durationchange="updateDuration"
     @timeupdate="updateProgress"
     @ended="ended"
     preload>
-
+    <source v-for="src in srcs" :src="src.url" :type="src.type">
   </audio>
 </template>
 
 <script>
 import {mapState} from 'vuex'
-import backend from '@/audio/backend'
 import url from '@/utils/url'
+import formats from '@/audio/formats'
 
 // import logger from '@/logging'
 
@@ -34,31 +33,43 @@ export default {
       volume: state => state.player.volume,
       looping: state => state.player.looping
     }),
-    url: function () {
+    srcs: function () {
       let file = this.track.files[0]
       if (!file) {
         this.$store.dispatch('player/trackErrored')
-        return null
+        return []
       }
-      let path = backend.absoluteUrl(file.path)
+      let sources = [
+        {type: file.mimetype, url: file.path}
+      ]
+      formats.formats.forEach(f => {
+        if (f !== file.mimetype) {
+          let format = formats.formatsMap[f]
+          let url = `/api/v1/trackfiles/transcode/?track_file=${file.id}&to=${format}`
+          sources.push({type: f, url: url})
+        }
+      })
       if (this.$store.state.auth.authenticated) {
         // we need to send the token directly in url
         // so authentication can be checked by the backend
         // because for audio files we cannot use the regular Authentication
         // header
-        path = url.updateQueryString(path, 'jwt', this.$store.state.auth.token)
+        sources.forEach(e => {
+          e.url = url.updateQueryString(e.url, 'jwt', this.$store.state.auth.token)
+        })
       }
-      return path
+      return sources
     }
   },
   methods: {
     errored: function () {
       this.$store.dispatch('player/trackErrored')
     },
-    updateLoad: function () {
-
+    updateDuration: function (e) {
+      this.$store.commit('player/duration', this.$refs.audio.duration)
     },
     loaded: function () {
+      this.$refs.audio.volume = this.volume
       if (this.isCurrent) {
         this.$store.commit('player/duration', this.$refs.audio.duration)
         if (this.startTime) {
diff --git a/front/src/store/player.js b/front/src/store/player.js
index fb348042fa107bdef406406bf67aac596da3d4b9..df8d159f40b4fe3287f9f6252df0ecfdb9023830 100644
--- a/front/src/store/player.js
+++ b/front/src/store/player.js
@@ -50,7 +50,12 @@ export default {
   },
   getters: {
     durationFormatted: state => {
-      return time.parse(Math.round(state.duration))
+      let duration = parseInt(state.duration)
+      if (duration % 1 !== 0) {
+        return time.parse(0)
+      }
+      duration = Math.round(state.duration)
+      return time.parse(duration)
     },
     currentTimeFormatted: state => {
       return time.parse(Math.round(state.currentTime))