Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
132 changes: 62 additions & 70 deletions index.js
Original file line number Diff line number Diff line change
@@ -1,46 +1,43 @@
exports.render = render
exports.append = append
exports.mime = require('./lib/mime.json')

var debug = require('debug')('render-media')
var isAscii = require('is-ascii')
var MediaElementWrapper = require('mediasource')
var path = require('path')
var streamToBlobURL = require('stream-to-blob-url')
var videostream = require('videostream')

var VIDEOSTREAM_EXTS = [
const debug = require('debug')('render-media')
const isAscii = require('is-ascii')
const MediaElementWrapper = require('mediasource')
const ext = require('path').extname
const streamToBlobURL = require('stream-to-blob-url')
const videostream = require('videostream')
const mime = require('./lib/mime.json')

const VIDEOSTREAM_EXTS = [
'.m4a',
'.m4v',
'.mp4'
]

var MEDIASOURCE_VIDEO_EXTS = [
const MEDIASOURCE_VIDEO_EXTS = [
'.m4v',
'.mkv',
'.mp4',
'.webm'
]

var MEDIASOURCE_AUDIO_EXTS = [
const MEDIASOURCE_AUDIO_EXTS = [
'.m4a',
'.mp3'
]

var MEDIASOURCE_EXTS = [].concat(
const MEDIASOURCE_EXTS = [].concat(
MEDIASOURCE_VIDEO_EXTS,
MEDIASOURCE_AUDIO_EXTS
)

var AUDIO_EXTS = [
const AUDIO_EXTS = [
'.aac',
'.oga',
'.ogg',
'.wav',
'.flac'
]

var IMAGE_EXTS = [
const IMAGE_EXTS = [
'.bmp',
'.gif',
'.jpeg',
Expand All @@ -49,7 +46,7 @@ var IMAGE_EXTS = [
'.svg'
]

var IFRAME_EXTS = [
const IFRAME_EXTS = [
'.css',
'.html',
'.js',
Expand All @@ -60,30 +57,26 @@ var IFRAME_EXTS = [

// Maximum file length for which the Blob URL strategy will be attempted
// See: https://github.com/feross/render-media/issues/18
var MAX_BLOB_LENGTH = 200 * 1000 * 1000 // 200 MB
const MAX_BLOB_LENGTH = 200 * 1000 * 1000 // 200 MB

var MediaSource = typeof window !== 'undefined' && window.MediaSource
const MediaSource = typeof window !== 'undefined' && window.MediaSource

function render (file, elem, opts, cb) {
if (typeof opts === 'function') {
cb = opts
opts = {}
}
if (typeof opts === 'function') [cb, opts] = [opts, cb]
if (typeof elem === 'string') elem = document.querySelector(elem)

if (!opts) opts = {}
if (!cb) cb = function () {}
if (!cb) cb = () => {}

validateFile(file)
parseOpts(opts)

if (typeof elem === 'string') elem = document.querySelector(elem)

renderMedia(file, function (tagName) {
renderMedia(file, tagName => {
if (elem.nodeName !== tagName.toUpperCase()) {
var extname = path.extname(file.name).toLowerCase()
const extname = ext(file.name).toLowerCase()

throw new Error(
'Cannot render "' + extname + '" inside a "' +
elem.nodeName.toLowerCase() + '" element, expected "' + tagName + '"'
`Cannot render "${extname}" inside a "${elem.nodeName.toLowerCase()}" element, expected "${tagName}"`
)
}

Expand All @@ -92,34 +85,28 @@ function render (file, elem, opts, cb) {
}

function append (file, rootElem, opts, cb) {
if (typeof opts === 'function') {
cb = opts
opts = {}
}
if (typeof opts === 'function') [cb, opts] = [opts, cb]
if (typeof rootElem === 'string') rootElem = document.querySelector(rootElem)

if (!opts) opts = {}
if (!cb) cb = function () {}

validateFile(file)
parseOpts(opts)

if (typeof rootElem === 'string') rootElem = document.querySelector(rootElem)

if (rootElem && (rootElem.nodeName === 'VIDEO' || rootElem.nodeName === 'AUDIO')) {
throw new Error(
'Invalid video/audio node argument. Argument must be root element that ' +
'video/audio tag will be appended to.'
)
}

renderMedia(file, getElem, opts, done)

function getElem (tagName) {
if (tagName === 'video' || tagName === 'audio') return createMedia(tagName)
else return createElem(tagName)
}

function createMedia (tagName) {
var elem = createElem(tagName)
const elem = createElem(tagName)
if (opts.autoplay) elem.autoplay = true
if (opts.muted) elem.muted = true
if (opts.controls) elem.controls = true
Expand All @@ -128,39 +115,41 @@ function append (file, rootElem, opts, cb) {
}

function createElem (tagName) {
var elem = document.createElement(tagName)
const elem = document.createElement(tagName)
rootElem.appendChild(elem)
return elem
}

function done (err, elem) {
if (err && elem) elem.remove()
cb(err, elem)
if (cb) cb(err, elem)
}

renderMedia(file, getElem, opts, done)
}

function renderMedia (file, getElem, opts, cb) {
var extname = path.extname(file.name).toLowerCase()
var currentTime = 0
var elem
const extname = ext(file.name).toLowerCase()
let currentTime = 0
let elem

if (MEDIASOURCE_EXTS.indexOf(extname) >= 0) {
if (MEDIASOURCE_EXTS.includes(extname)) {
renderMediaSource()
} else if (AUDIO_EXTS.indexOf(extname) >= 0) {
} else if (AUDIO_EXTS.includes(extname)) {
renderAudio()
} else if (IMAGE_EXTS.indexOf(extname) >= 0) {
} else if (IMAGE_EXTS.includes(extname)) {
renderImage()
} else if (IFRAME_EXTS.indexOf(extname) >= 0) {
} else if (IFRAME_EXTS.includes(extname)) {
renderIframe()
} else {
tryRenderIframe()
}

function renderMediaSource () {
var tagName = MEDIASOURCE_VIDEO_EXTS.indexOf(extname) >= 0 ? 'video' : 'audio'
const tagName = MEDIASOURCE_VIDEO_EXTS.includes(extname) ? 'video' : 'audio'

if (MediaSource) {
if (VIDEOSTREAM_EXTS.indexOf(extname) >= 0) {
if (VIDEOSTREAM_EXTS.includes(extname)) {
useVideostream()
} else {
useMediaSource()
Expand All @@ -179,26 +168,26 @@ function renderMedia (file, getElem, opts, cb) {
}

function useMediaSource () {
debug('Use MediaSource API for ' + file.name)
debug(`Use MediaSource API for ${file.name}`)
prepareElem()
elem.addEventListener('error', fallbackToBlobURL)
elem.addEventListener('loadstart', onLoadStart)
elem.addEventListener('canplay', onCanPlay)

var wrapper = new MediaElementWrapper(elem)
var writable = wrapper.createWriteStream(getCodec(file.name))
const wrapper = new MediaElementWrapper(elem)
const writable = wrapper.createWriteStream(getCodec(file.name))
file.createReadStream().pipe(writable)

if (currentTime) elem.currentTime = currentTime
}

function useBlobURL () {
debug('Use Blob URL for ' + file.name)
debug(`Use Blob URL for ${file.name}`)
prepareElem()
elem.addEventListener('error', fatalError)
elem.addEventListener('loadstart', onLoadStart)
elem.addEventListener('canplay', onCanPlay)
getBlobURL(file, function (err, url) {
getBlobURL(file, (err, url) => {
if (err) return fatalError(err)
elem.src = url
if (currentTime) elem.currentTime = currentTime
Expand All @@ -222,8 +211,7 @@ function renderMedia (file, getElem, opts, cb) {
file.length, opts.maxBlobLength
)
return fatalError(new Error(
'File length too large for Blob URL approach: ' + file.length +
' (max: ' + opts.maxBlobLength + ')'
`File length too large for Blob URL approach: ${file.length} (max: ${opts.maxBlobLength})`
))
}

Expand All @@ -237,7 +225,7 @@ function renderMedia (file, getElem, opts, cb) {
if (!elem) {
elem = getElem(tagName)

elem.addEventListener('progress', function () {
elem.addEventListener('progress', () => {
currentTime = elem.currentTime
})
}
Expand All @@ -246,7 +234,7 @@ function renderMedia (file, getElem, opts, cb) {

function renderAudio () {
elem = getElem('audio')
getBlobURL(file, function (err, url) {
getBlobURL(file, (err, url) => {
if (err) return fatalError(err)
elem.addEventListener('error', fatalError)
elem.addEventListener('loadstart', onLoadStart)
Expand All @@ -267,7 +255,7 @@ function renderMedia (file, getElem, opts, cb) {

function renderImage () {
elem = getElem('img')
getBlobURL(file, function (err, url) {
getBlobURL(file, (err, url) => {
if (err) return fatalError(err)
elem.src = url
elem.alt = file.name
Expand All @@ -276,7 +264,7 @@ function renderMedia (file, getElem, opts, cb) {
}

function renderIframe () {
getBlobURL(file, function (err, url) {
getBlobURL(file, (err, url) => {
if (err) return fatalError(err)

if (extname !== '.pdf') {
Expand All @@ -300,10 +288,10 @@ function renderMedia (file, getElem, opts, cb) {
function tryRenderIframe () {
debug('Unknown file extension "%s" - will attempt to render into iframe', extname)

var str = ''
let str = ''
file.createReadStream({ start: 0, end: 1000 })
.setEncoding('utf8')
.on('data', function (chunk) {
.on('data', chunk => {
str += chunk
})
.on('end', done)
Expand All @@ -315,21 +303,21 @@ function renderMedia (file, getElem, opts, cb) {
renderIframe()
} else {
debug('File extension "%s" appears non-ascii, will not render.', extname)
cb(new Error('Unsupported file type "' + extname + '": Cannot append to DOM'))
cb(new Error(`Unsupported file type "${extname}": Cannot append to DOM`))
}
}
}

function fatalError (err) {
err.message = 'Error rendering file "' + file.name + '": ' + err.message
err.message = `Error rendering file "${file.name}": ${err.message}`
debug(err.message)
cb(err)
}
}

function getBlobURL (file, cb) {
var extname = path.extname(file.name).toLowerCase()
streamToBlobURL(file.createReadStream(), exports.mime[extname], cb)
const extname = ext(file.name).toLowerCase()
streamToBlobURL(file.createReadStream(), mime[extname], cb)
}

function validateFile (file) {
Expand All @@ -345,7 +333,7 @@ function validateFile (file) {
}

function getCodec (name) {
var extname = path.extname(name).toLowerCase()
const extname = ext(name).toLowerCase()
return {
'.m4a': 'audio/mp4; codecs="mp4a.40.5"',
'.m4v': 'video/mp4; codecs="avc1.640029, mp4a.40.5"',
Expand All @@ -362,3 +350,7 @@ function parseOpts (opts) {
if (opts.controls == null) opts.controls = true
if (opts.maxBlobLength == null) opts.maxBlobLength = MAX_BLOB_LENGTH
}

exports.render = render
exports.append = append
exports.mime = mime
Loading