diff --git a/platypush/backend/http/__init__.py b/platypush/backend/http/__init__.py
index 4712ec30..722282fe 100644
--- a/platypush/backend/http/__init__.py
+++ b/platypush/backend/http/__init__.py
@@ -11,12 +11,12 @@ import time
 
 from multiprocessing import Process
 from flask import Flask, Response, abort, jsonify, request as http_request, \
-    render_template, send_from_directory
+    render_template, send_from_directory, make_response
 
 from redis import Redis
 
 from platypush.config import Config
-from platypush.context import get_backend, get_or_create_event_loop
+from platypush.context import get_backend, get_plugin, get_or_create_event_loop
 from platypush.message import Message
 from platypush.message.event import Event, StopEvent
 from platypush.message.event.web.widget import WidgetUpdateEvent
@@ -371,7 +371,7 @@ class HttpBackend(Backend):
         def get_media_id(source):
             return hashlib.sha1(source.encode()).hexdigest()
 
-        def register_media(source):
+        def register_media(source, subtitles=None):
             media_id = get_media_id(source)
             media_url = get_media_url(media_id)
 
@@ -379,8 +379,10 @@ class HttpBackend(Backend):
                 if media_id in media_map:
                     return media_map[media_id]
 
-                media_hndl = MediaHandler.build(source, url=media_url)
+                media_hndl = MediaHandler.build(source, url=media_url,
+                                                subtitles=subtitles)
                 media_map[media_id] = media_hndl
+                media_hndl.media_id = media_id
 
             self.logger.info('Streaming "{}" on {}'.format(source, media_url))
             return media_hndl
@@ -434,7 +436,6 @@ class HttpBackend(Backend):
 
                 if not to_bytes:
                     to_bytes = content_length-1
-                    # to_bytes = from_bytes + self._DEFAULT_STREAMING_BLOCK_SIZE
                     content_length -= from_bytes
                 else:
                     to_bytes = int(to_bytes)
@@ -450,11 +451,118 @@ class HttpBackend(Backend):
 
             headers['Content-Length'] = content_length
 
-            return Response(media_hndl.get_data(
-                from_bytes=from_bytes, to_bytes=to_bytes,
-                chunk_size=self._DEFAULT_STREAMING_CHUNK_SIZE),
-                status_code, headers=headers, mimetype=headers['Content-Type'],
-                direct_passthrough=True)
+            if 'webplayer' in request.args:
+                return render_template('webplayer.html',
+                                       media_url=media_hndl.url.replace(
+                                           self.remote_base_url, ''),
+                                       media_type=media_hndl.mime_type,
+                                       subtitles_url='/media/subtitles/{}.srt'.
+                                       format(media_id))
+            else:
+                return Response(media_hndl.get_data(
+                    from_bytes=from_bytes, to_bytes=to_bytes,
+                    chunk_size=self._DEFAULT_STREAMING_CHUNK_SIZE),
+                    status_code, headers=headers, mimetype=headers['Content-Type'],
+                    direct_passthrough=True)
+
+
+        def add_subtitles(media_id, req):
+            """
+            This route can be used to download and/or expose subtitles files
+            associated to a media file
+            """
+
+            media_hndl = media_map.get(media_id)
+            if not media_hndl:
+                raise FileNotFoundError('{} is not a registered media_id'.
+                                        format(media_id))
+
+            subfile = None
+            if req.data:
+                try:
+                    subfile = json.loads(req.data.decode('utf-8')) \
+                        .get('filename')
+                    if not subfile:
+                        raise AttributeError
+                except Exception as e:
+                    raise AttributeError(400, 'No filename in the request: {}'
+                                         .format(str(e)))
+
+            if not subfile:
+                if not media_hndl.path:
+                    raise NotImplementedError(
+                        'Subtitles are currently only supported for ' +
+                        'local media files')
+
+                try:
+                    subtitles = get_plugin('media.subtitles').get_subtitles(
+                        media_hndl.path)
+                except Exception as e:
+                    raise RuntimeError('Could not get subtitles: {}'.
+                                       format(str(e)))
+
+                if not subtitles:
+                    raise FileNotFoundError(
+                        'No subtitles found for resource {}'.format(
+                            media_hndl.path))
+
+                subfile = get_plugin('media.subtitles').download(
+                    link=subtitles[0].get('SubDownloadLink'),
+                    media_resource=media_hndl.path).get('filename')
+
+            media_hndl.set_subtitles(subfile)
+            return {
+                'filename': subfile,
+                'url': self.remote_base_url + '/media/subtitles/' + media_id + '.srt',
+            }
+
+        def remove_subtitles(media_id):
+            media_hndl = media_map.get(media_id)
+            if not media_hndl:
+                raise FileNotFoundError('{} is not a registered media_id'.
+                                        format(media_id))
+
+            if not media_hndl.subtitles:
+                raise FileNotFoundError('{} has no subtitles attached'.
+                                        format(media_id))
+
+            media_hndl.remove_subtitles()
+            return {}
+
+
+        @app.route('/media/subtitles/<media_id>.srt', methods=['GET', 'PUT', 'DELETE'])
+        def handle_subtitles(media_id):
+            """
+            This route can be used to download and/or expose subtitle files
+            associated to a media file
+            """
+
+            if http_request.method == 'GET':
+                media_hndl = media_map.get(media_id)
+                if not media_hndl:
+                    abort(404, 'No such media')
+
+                if not media_hndl.subtitles:
+                    abort(404, 'The media has no subtitles attached')
+
+                return send_from_directory(
+                    os.path.dirname(media_hndl.subtitles),
+                    os.path.basename(media_hndl.subtitles),
+                    mimetype='text/vtt')
+
+            try:
+                if http_request.method == 'DELETE':
+                    return jsonify(remove_subtitles(media_id))
+                else:
+                    return jsonify(add_subtitles(media_id, http_request))
+            except FileNotFoundError as e:
+                abort(404, str(e))
+            except AttributeError as e:
+                abort(400, str(e))
+            except NotImplementedError as e:
+                abort(422, str(e))
+            except Exception as e:
+                abort(500, str(e))
 
         @app.route('/media', methods=['GET', 'PUT'])
         def add_or_get_media():
@@ -476,9 +584,14 @@ class HttpBackend(Backend):
             if not source:
                 abort(400, 'The request does not contain any source')
 
+            subtitles = args.get('subtitles')
             try:
-                media_hndl = register_media(source)
-                return jsonify(dict(media_hndl))
+                media_hndl = register_media(source, subtitles)
+                ret = dict(media_hndl)
+                if media_hndl.subtitles:
+                    ret['subtitles_url'] = self.remote_base_url + \
+                        '/media/subtitles/' + media_hndl.media_id + '.srt'
+                return jsonify(ret)
             except FileNotFoundError as e:
                 abort(404, str(e))
             except AttributeError as e:
diff --git a/platypush/backend/http/media/handlers/__init__.py b/platypush/backend/http/media/handlers/__init__.py
index ae1d04a5..419e50e9 100644
--- a/platypush/backend/http/media/handlers/__init__.py
+++ b/platypush/backend/http/media/handlers/__init__.py
@@ -7,7 +7,8 @@ class MediaHandler:
     prefix_handlers = []
 
     def __init__(self, source, filename=None,
-                 mime_type='application/octet-stream', name=None, url=None):
+                 mime_type='application/octet-stream', name=None, url=None,
+                 subtitles=None):
         matched_handlers = [hndl for hndl in self.prefix_handlers
                             if source.startswith(hndl)]
 
@@ -18,10 +19,12 @@ class MediaHandler:
                                       self.prefix_handlers))
 
         self.name = name
+        self.path = None
         self.filename = name
         self.source = source
         self.url = url
         self.mime_type = mime_type
+        self.subtitles = subtitles
         self.content_length = 0
         self._matched_handler = matched_handlers[0]
 
@@ -42,8 +45,15 @@ class MediaHandler:
     def get_data(self, from_bytes=None, to_bytes=None, chunk_size=None):
         raise NotImplementedError()
 
+    def set_subtitles(self, subtitles_file):
+        self.subtitles = subtitles_file
+
+    def remove_subtitles(self):
+        self.subtitles = None
+
     def __iter__(self):
-        for attr in ['name', 'source', 'mime_type', 'url', 'prefix_handlers']:
+        for attr in ['name', 'source', 'mime_type', 'url', 'subtitles',
+                     'prefix_handlers']:
             yield (attr, getattr(self, attr))
 
 
diff --git a/platypush/backend/http/static/js/media.js b/platypush/backend/http/static/js/media.js
index 7b0ce9bd..225b7d66 100644
--- a/platypush/backend/http/static/js/media.js
+++ b/platypush/backend/http/static/js/media.js
@@ -50,6 +50,7 @@ $(document).ready(function() {
                     'icon': 'stop',
                     'html': 'Media playback stopped',
                 });
+                $mediaSearchSubtitles.hide();
                 break;
         }
     };
@@ -105,6 +106,7 @@ $(document).ready(function() {
     };
 
     const startStreamingTorrent = function(torrent) {
+        // TODO support for subtitles download on torrent metadata received
         return new Promise((resolve, reject) => {
             execute(
                 {
@@ -127,7 +129,7 @@ $(document).ready(function() {
         });
     };
 
-    const startStreaming = function(media) {
+    const startStreaming = function(media, subtitles) {
         if (media.startsWith('magnet:?')) {
             return new Promise((resolve, reject) => {
                 startStreamingTorrent(media)
@@ -141,12 +143,20 @@ $(document).ready(function() {
                 type: 'PUT',
                 url: '/media',
                 contentType: 'application/json',
-                data: JSON.stringify({ source: media }),
+                data: JSON.stringify({
+                    'source': media,
+                    'subtitles': subtitles,
+                }),
 
                 complete: (xhr, textStatus) => {
                     if (xhr.status == 200) {
                         var uri = xhr.responseJSON.url.match(/https?:\/\/[^\/]+(\/media\/.*)/)[1]
-                        resolve(uri);
+                        var subtitles;
+                        if ('subtitles_url' in xhr.responseJSON) {
+                            subtitles = xhr.responseJSON.subtitles_url.match(/https?:\/\/[^\/]+(\/media\/.*)/)[1]
+                        }
+
+                        resolve(uri, subtitles);
                     } else {
                         reject(Error(xhr.responseText));
                     }
@@ -168,7 +178,7 @@ $(document).ready(function() {
     const getSubtitles = function(resource) {
         return new Promise((resolve, reject) => {
             if (!window.config.media.subtitles) {
-                return;  // media.subtitles plugin not configured
+                resolve();  // media.subtitles plugin not configured
             }
 
             run({
@@ -233,11 +243,9 @@ $(document).ready(function() {
 
     const playInBrowser = (resource, subtitles) => {
         return new Promise((resolve, reject) => {
-            // TODO support for subtitles in local player
-
-            startStreaming(resource).then((url) => {
-                window.open(url, '_blank');
-                resolve(url);
+            startStreaming(resource, subtitles).then((url, subtitles) => {
+                window.open(url + '?webplayer', '_blank');
+                resolve(url, subtitles);
             }).catch((xhr, status, error) => {
                 reject(xhr.responseText);
             });
@@ -299,16 +307,19 @@ $(document).ready(function() {
 
             var subtitlesConf = window.config.media.subtitles;
 
-            // TODO populate the subtitles panel and show the subtitles button
-            if (subtitlesConf && 'language' in subtitlesConf) {
-                tryFetchSubtitles(resource).then((subtitles) => {
-                    if (!subtitles) {
-                        showError('Cannot get subtitles: ' + error);
-                        _play(resource).finally(onVideoReady);
-                    } else {
-                        _play(resource, subtitles).finally(onVideoReady);
-                    }
-                });
+            if (subtitlesConf) {
+                populateSubtitlesModal(resource);
+
+                if ('language' in subtitlesConf) {
+                    tryFetchSubtitles(resource).then((subtitles) => {
+                        if (!subtitles) {
+                            showError('Cannot get subtitles');
+                            _play(resource).finally(onVideoReady);
+                        } else {
+                            _play(resource, subtitles).finally(onVideoReady);
+                        }
+                    });
+                }
             } else {
                 _play(resource).finally(onVideoReady);
             }
@@ -346,7 +357,7 @@ $(document).ready(function() {
             onVideoLoading();
             startStreaming(resource)
                 .then((url) => {
-                    url = url + '?download=1'
+                    url = url + '?download'
                     window.open(url, '_blank');
                     resolve(url);
                 })
@@ -359,6 +370,58 @@ $(document).ready(function() {
         });
     };
 
+    const populateSubtitlesModal = (resource) => {
+        $mediaSubtitlesMessage.text('Loading subtitles...');
+        $mediaSubtitlesResults.text('');
+        $mediaSubtitlesMessage.show();
+        $mediaSubtitlesResultsContainer.hide();
+
+        getSubtitles(resource).then((subs) => {
+            if (!subs.length) {
+                $mediaSubtitlesMessage.text('No subtitles found');
+                return;
+            }
+
+            $mediaSearchSubtitles.show();
+            for (var sub of subs) {
+                var flagCode;
+                if ('ISO639' in sub) {
+                    switch(sub.ISO639) {
+                        case 'en': flagCode = 'gb'; break;
+                        default: flagCode = sub.ISO639; break;
+                    }
+                }
+
+                var $subContainer = $('<div></div>').addClass('row media-subtitle-container')
+                    .data('download-link', sub.SubDownloadLink)
+                    .data('resource', resource);
+
+                var $subFlagIconContainer = $('<div></div>').addClass('one column');
+                var $subFlagIcon = $('<span></span>')
+                    .addClass(flagCode ? 'flag-icon flag-icon-' + flagCode : (
+                        sub.IsLocal ? 'fa fa-download' : ''))
+                    .text(!(flagCode || sub.IsLocal) ? '?' : '');
+
+                var $subMovieName = $('<div></div>').addClass('five columns')
+                    .text(sub.MovieName);
+
+                var $subFileName = $('<div></div>').addClass('six columns')
+                    .text(sub.SubFileName);
+
+                $subFlagIcon.appendTo($subFlagIconContainer);
+                $subFlagIconContainer.appendTo($subContainer);
+                $subMovieName.appendTo($subContainer);
+                $subFileName.appendTo($subContainer);
+                $subContainer.appendTo($mediaSubtitlesResults);
+            }
+
+            $mediaSubtitlesMessage.hide();
+            $mediaSubtitlesResultsContainer.show();
+        }).catch((error) => {
+            $mediaSubtitlesMessage.text('Unable to load subtitles: ' + error.message);
+        });
+    };
+
     const initBindings = function() {
         window.registerEventListener(onEvent);
         $searchForm.on('submit', function(event) {
@@ -531,61 +594,8 @@ $(document).ready(function() {
                 });
         });
 
-        $mediaSearchSubtitles.on('mouseup touchend', () => {
-            var resource = $mediaSearchSubtitles.data('resource');
-            if (!resource) {
-                return;
-            }
-
-            $mediaSubtitlesMessage.text('Loading subtitles...');
-            $mediaSubtitlesResults.text('');
-            $mediaSubtitlesMessage.show();
-            $mediaSubtitlesResultsContainer.hide();
-
-            getSubtitles(resource).then((subs) => {
-                if (!subs.length) {
-                    $mediaSubtitlesMessage.text('No subtitles found');
-                    return;
-                }
-
-                for (var sub of subs) {
-                    var flagCode;
-                    if ('ISO639' in sub) {
-                        switch(sub.ISO639) {
-                            case 'en': flagCode = 'gb'; break;
-                            default: flagCode = sub.ISO639; break;
-                        }
-                    }
-
-                    var $subContainer = $('<div></div>').addClass('row media-subtitle-container')
-                        .data('download-link', sub.SubDownloadLink)
-                        .data('resource', resource);
-
-                    var $subFlagIconContainer = $('<div></div>').addClass('one column');
-                    var $subFlagIcon = $('<span></span>')
-                        .addClass(flagCode ? 'flag-icon flag-icon-' + flagCode : (
-                            sub.IsLocal ? 'fa fa-download' : ''))
-                        .text(!(flagCode || sub.IsLocal) ? '?' : '');
-
-                    var $subMovieName = $('<div></div>').addClass('five columns')
-                        .text(sub.MovieName);
-
-                    var $subFileName = $('<div></div>').addClass('six columns')
-                        .text(sub.SubFileName);
-
-                    $subFlagIcon.appendTo($subFlagIconContainer);
-                    $subFlagIconContainer.appendTo($subContainer);
-                    $subMovieName.appendTo($subContainer);
-                    $subFileName.appendTo($subContainer);
-                    $subContainer.appendTo($mediaSubtitlesResults);
-                }
-
-                $mediaSubtitlesMessage.hide();
-                $mediaSubtitlesResultsContainer.show();
-            }).catch((error) => {
-                $mediaSubtitlesMessage.text('Unable to load subtitles: ' + error.message);
-            });
-        });
+        // $mediaSearchSubtitles.on('mouseup touchend', () => {
+        // });
     };
 
     const initRemoteDevices = function() {
diff --git a/platypush/backend/http/templates/webplayer.html b/platypush/backend/http/templates/webplayer.html
new file mode 100644
index 00000000..62ad7ff6
--- /dev/null
+++ b/platypush/backend/http/templates/webplayer.html
@@ -0,0 +1,19 @@
+<script>
+    window.addEventListener('load', () => {
+        var video = document.querySelector('#video');
+        video.addEventListener('load', () => {
+            var tracks = video.textTracks;
+            if (tracks.length) {
+                tracks[0].mode = 'showing';
+            }
+        });
+    }, false);
+</script>
+
+<video id="video" controls autoplay preload="metadata">
+    <source src="{{ media_url }}" type="{{ media_type }}">
+    {% if subtitles_url %}
+    <track label="Subtitles" kind="subtitles" src="{{ subtitles_url }}" default>
+    {% endif %}
+</video>
+