├── .gitignore ├── media ├── audio.mp3 └── video.mp4 ├── test ├── common.js ├── setup.js ├── audio.spec.js └── video.spec.js ├── .babelrc ├── rollup.config.js ├── tools └── blob-generator.js ├── LICENSE ├── package.json ├── index.html ├── lib ├── index.js └── media.js ├── CHANGELOG.md ├── README.md └── yarn.lock /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | .DS_Store 3 | 4 | # Ignore JetBrains Editor 5 | .idea/ 6 | -------------------------------------------------------------------------------- /media/audio.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/video-dev/can-autoplay/HEAD/media/audio.mp3 -------------------------------------------------------------------------------- /media/video.mp4: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/video-dev/can-autoplay/HEAD/media/video.mp4 -------------------------------------------------------------------------------- /test/common.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | mockPlay: function (implementation) { 3 | window.HTMLMediaElement.prototype.play = () => implementation() 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | ["env", { 4 | "targets": { 5 | "browsers": ["last 2 versions", "safari >= 8", "ie 11"] 6 | } 7 | }] 8 | ] 9 | } 10 | -------------------------------------------------------------------------------- /test/setup.js: -------------------------------------------------------------------------------- 1 | let jsdom = require('jsdom') 2 | let {JSDOM} = jsdom 3 | let exposedProperties = ['window', 'navigator'] 4 | let dom = new JSDOM('') 5 | 6 | global.window = dom.window 7 | global.navigator = { 8 | userAgent: 'node.js' 9 | } 10 | global.Blob = function () {} 11 | global.URL = {createObjectURL: function () {}} 12 | 13 | Object.keys(dom.window).forEach(property => { 14 | if (typeof global[property] === 'undefined') { 15 | exposedProperties.push(property) 16 | global[property] = dom.window[property] 17 | } 18 | }) 19 | -------------------------------------------------------------------------------- /test/audio.spec.js: -------------------------------------------------------------------------------- 1 | import test from 'ava' 2 | import { mockPlay } from './common' 3 | import canAutoplay from '../lib/index' 4 | 5 | test.serial('returns true for available audio auto-playback', t => { 6 | mockPlay(() => Promise.resolve()) 7 | return canAutoplay.audio().then(({result}) => t.true(result)) 8 | }) 9 | 10 | test.serial('returns false for unavailable audio auto-playback', t => { 11 | mockPlay(() => Promise.reject(new Error())) 12 | return canAutoplay.audio().then(({result}) => t.false(result)) 13 | }) 14 | 15 | test.serial('returns error object', t => { 16 | const message = 'Aborted.' 17 | mockPlay(() => Promise.reject(new Error(message))) 18 | return canAutoplay.audio().then(({error}) => t.is(error.message, message)) 19 | }) 20 | -------------------------------------------------------------------------------- /rollup.config.js: -------------------------------------------------------------------------------- 1 | import babel from 'rollup-plugin-babel'; 2 | import pkg from './package.json'; 3 | 4 | export default [ 5 | // Browser-friendly UMD build 6 | // CommonJS (for Node) and ES module (for bundlers) build. 7 | { 8 | input: 'lib/index.js', 9 | output: [ 10 | { file: 'build/can-autoplay.js', format: 'umd', name: 'canAutoplay' }, 11 | { file: pkg.main, format: 'cjs' }, 12 | { file: pkg.module, format: 'es' } 13 | ], 14 | plugins: [ 15 | babel({ 16 | babelrc: false, 17 | exclude: ['node_modules/**'], 18 | presets: [ 19 | ['env', { 20 | targets: { 21 | browsers: ['last 2 versions', 'safari >= 8', 'ie 11'] 22 | }, 23 | modules: false 24 | }] 25 | ] 26 | }) 27 | ] 28 | } 29 | ]; 30 | -------------------------------------------------------------------------------- /tools/blob-generator.js: -------------------------------------------------------------------------------- 1 | const fs = require('fs'), 2 | mime = require('mime-types'), 3 | path = require('path'); 4 | 5 | let audioFile = fs.readFileSync(path.resolve(process.cwd(), process.env.AUDIO)), 6 | audioMimeType = mime.lookup(process.env.AUDIO), 7 | videoFile = fs.readFileSync(path.resolve(process.cwd(), process.env.VIDEO)), 8 | videoMimeType = mime.lookup(process.env.VIDEO); 9 | 10 | let code = `/* global Blob */ 11 | // This file is generated. Do not edit this file directly. 12 | // Command: 'npm run generate' should be used to update the content. 13 | 14 | /** 15 | * @type {Blob} 16 | */ 17 | export const AUDIO = new Blob([new Uint8Array([${[...audioFile].join(', ')}])], {type: '${audioMimeType}'}) 18 | 19 | /** 20 | * @type {Blob} 21 | */ 22 | export const VIDEO = new Blob([new Uint8Array([${[...videoFile].join(', ')}])], {type: '${videoMimeType}'}) 23 | `; 24 | 25 | console.log('Writing file...'); 26 | 27 | fs.writeFileSync( 28 | path.resolve(process.cwd(), process.env.OUTPUT), 29 | code, 30 | {encoding: 'utf8'} 31 | ); 32 | 33 | 34 | 35 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 video-dev 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "can-autoplay", 3 | "version": "3.0.2", 4 | "main": "build/can-autoplay.cjs.js", 5 | "module": "build/can-autoplay.es.js", 6 | "author": "Caio Gondim ", 7 | "license": "MIT", 8 | "private": false, 9 | "devDependencies": { 10 | "ava": "^0.25.0", 11 | "babel-preset-env": "^1.6.1", 12 | "babel-register": "^6.26.0", 13 | "doctoc": "^1.3.0", 14 | "google-closure-compiler-js": "^20170910.0.1", 15 | "jsdom": "^11.4.0", 16 | "mime-types": "^2.1.17", 17 | "rollup": "^0.55.1", 18 | "rollup-plugin-babel": "^3.0.3", 19 | "standard": "^10.0.3" 20 | }, 21 | "scripts": { 22 | "build": "rollup -c", 23 | "generate": "AUDIO=./media/audio.mp3 VIDEO=./media/video.mp4 OUTPUT=./lib/media.js node ./tools/blob-generator.js", 24 | "minify": "google-closure-compiler-js 2>/dev/null build/can-autoplay.js > build/can-autoplay.min.js", 25 | "watch": "rollup -c -w", 26 | "lint": "standard 'lib/**/*.js' 'test/**/*.js'", 27 | "doc": "doctoc README.md", 28 | "test": "ava && yarn run lint", 29 | "prepublishOnly": "yarn test && yarn build && yarn minify" 30 | }, 31 | "ava": { 32 | "files": [ 33 | "test/*.spec.js" 34 | ], 35 | "require": [ 36 | "babel-register", 37 | "./test/setup.js" 38 | ] 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /test/video.spec.js: -------------------------------------------------------------------------------- 1 | import test from 'ava' 2 | import { mockPlay } from './common' 3 | import canAutoplay from '../lib/index' 4 | 5 | test.serial('returns true for available video auto-playback', t => { 6 | mockPlay(() => Promise.resolve()) 7 | return canAutoplay.video().then(({result}) => t.true(result)) 8 | }) 9 | 10 | test.serial('returns false for unavailable video auto-playback', t => { 11 | mockPlay(() => Promise.reject(new Error())) 12 | return canAutoplay.video().then(({result}) => t.false(result)) 13 | }) 14 | 15 | test.serial('returns true for old browsers', t => { 16 | mockPlay(() => undefined) 17 | return canAutoplay.video().then(({result}) => t.true(result)) 18 | }) 19 | 20 | test.serial('reaches timeout with a false result', t => { 21 | t.plan(2) 22 | mockPlay(() => new Promise(resolve => undefined)) 23 | return canAutoplay.video({timeout: 0}).then(({result, error}) => { 24 | t.false(result) 25 | t.regex(error.message, /timeout/i) 26 | }) 27 | }) 28 | 29 | test.serial('returns error object', t => { 30 | const message = 'Aborted.' 31 | mockPlay(() => Promise.reject(new Error(message))) 32 | return canAutoplay.video().then(({error}) => t.is(error.message, message)) 33 | }) 34 | 35 | test.serial('return null error by default', t => { 36 | mockPlay(() => Promise.resolve()) 37 | return canAutoplay.video().then(({error}) => t.is(error, null)) 38 | }) 39 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Demo: can-autoplay 6 | 7 | 8 |

can-autoplay

9 | 17 | 18 | 19 | 20 | 49 | 50 | 51 | -------------------------------------------------------------------------------- /lib/index.js: -------------------------------------------------------------------------------- 1 | /* global URL */ 2 | import * as Media from './media' 3 | 4 | function setupDefaultValues (options) { 5 | return Object.assign({ 6 | muted: false, 7 | timeout: 250, 8 | inline: false 9 | }, options) 10 | } 11 | 12 | function startPlayback ({muted, timeout, inline}, elementCallback) { 13 | let {element, source} = elementCallback() 14 | let playResult 15 | let timeoutId 16 | let sendOutput 17 | 18 | element.muted = muted 19 | if (muted === true) { 20 | element.setAttribute('muted', 'muted') 21 | } 22 | // indicates that the video is to be played "inline", 23 | // that is within the element's playback area. 24 | if (inline === true) { 25 | element.setAttribute('playsinline', 'playsinline') 26 | } 27 | 28 | element.src = source 29 | 30 | return new Promise(resolve => { 31 | playResult = element.play() 32 | timeoutId = setTimeout(() => { 33 | sendOutput(false, new Error(`Timeout ${timeout} ms has been reached`)) 34 | }, timeout) 35 | sendOutput = (result, error = null) => { 36 | // Clean up to avoid MediaElementLeak 37 | element.remove() 38 | element.srcObject = null 39 | 40 | clearTimeout(timeoutId) 41 | resolve({result, error}) 42 | } 43 | 44 | if (playResult !== undefined) { 45 | playResult 46 | .then(() => sendOutput(true)) 47 | .catch(playError => sendOutput(false, playError)) 48 | } else { 49 | sendOutput(true) 50 | } 51 | }) 52 | } 53 | 54 | // 55 | // API 56 | // 57 | 58 | function video (options) { 59 | options = setupDefaultValues(options) 60 | return startPlayback(options, () => { 61 | return { 62 | element: document.createElement('video'), 63 | source: URL.createObjectURL(Media.VIDEO) 64 | } 65 | }) 66 | } 67 | 68 | function audio (options) { 69 | options = setupDefaultValues(options) 70 | return startPlayback(options, () => { 71 | return { 72 | element: document.createElement('audio'), 73 | source: URL.createObjectURL(Media.AUDIO) 74 | } 75 | }) 76 | } 77 | 78 | export default {audio, video} 79 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | All notable changes to this project will be documented in this file. 4 | 5 | The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/) 6 | and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html). 7 | 8 | ## [3.0.0] - 2018-02-25 9 | 10 | - Added pre-compilation step with Blob Generator 11 | - Changed core implementation where it's now based on blobs 12 | - Changed small media structure where media is stored as a separate local file 13 | - Changed unit tests to work with blob based implementation 14 | - Changed Rollup build to have all builds unified in a single step 15 | - Changed AVA dependency to `0.25.0` 16 | 17 | ## [2.3.2] - 2018-02-15 18 | 19 | - Fixed build by adding missed minified file 20 | 21 | ## [2.3.1] - 2018-02-15 22 | 23 | - Fixed build by adding missed bundled files for CommonJS and ES6 variants 24 | 25 | ## [2.3.0] - 2018-02-15 26 | 27 | - Added option `blob` to use blob as media source instead of base64 28 | 29 | ## [2.2.1] - 2018-02-13 30 | 31 | - Fixed build by adding missed bundled files 32 | 33 | ## [2.2.0] - 2018-02-13 34 | 35 | - Added option `inline` to check auto-play for inline playback 36 | 37 | ## [2.1.1] - 2018-02-02 38 | 39 | - Added notes about media files used in the project 40 | - Fixed imports to provide wrapper Object 41 | 42 | ## [2.1.0] - 2018-02-01 43 | 44 | - Added ES5/ES6 versions of the library for bundlers 45 | 46 | ## [2.0.1] - 2017-12-11 47 | 48 | - Changed documentation to include latest API changes 49 | 50 | ## [2.0.0] - 2017-12-04 51 | 52 | - Added error for timeout 53 | - Added changelog tracking 54 | - Changed DOM test framework to JSDom 55 | - Changed API to use `audio/video` methods with same payload 56 | - Changed playback detection to rely on browser's `play()` Promise API 57 | - Changed documentation to include examples with Promise API 58 | - Removed `videoMuted` method in favor of generic API for `video` and `audio` 59 | - Removed `DOM` invalidation 60 | 61 | ## [1.0.1] - 2017-11-17 62 | 63 | - Added minified version of the library 64 | - Added size badge 65 | - Added more examples 66 | - Changed `Ava` test output 67 | 68 | ## [1.0.0] - 2017-11-16 69 | 70 | - Initial release 71 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # can-autoplay.js 2 | 3 | The auto-play feature detection in HTMLMediaElement (`