├── .gitignore ├── LICENSE ├── README.md ├── dist ├── videocompositor.commonjs2.js └── videocompositor.js ├── doc ├── ast │ └── source │ │ ├── audiomanager.js.json │ │ ├── effect.js.json │ │ ├── effectmanager.js.json │ │ ├── main.js.json │ │ └── sources │ │ ├── canvassource.js.json │ │ ├── imagesource.js.json │ │ ├── mediasource.js.json │ │ └── videosource.js.json ├── badge.svg ├── class │ └── src │ │ ├── audiomanager.js~AudioManager.html │ │ ├── effect.js~Effect.html │ │ ├── effectmanager.js~EffectManager.html │ │ ├── main.js~VideoCompositor.html │ │ └── sources │ │ ├── canvassource.js~CanvasSource.html │ │ ├── imagesource.js~ImageSource.html │ │ ├── mediasource.js~MediaSource.html │ │ └── videosource.js~VideoSource.html ├── coverage.json ├── css │ ├── prettify-tomorrow.css │ └── style.css ├── dump.json ├── file │ └── src │ │ ├── audiomanager.js.html │ │ ├── effect.js.html │ │ ├── effectmanager.js.html │ │ ├── main.js.html │ │ └── sources │ │ ├── canvassource.js.html │ │ ├── imagesource.js.html │ │ ├── mediasource.js.html │ │ └── videosource.js.html ├── identifiers.html ├── image │ ├── badge.svg │ ├── github.png │ └── search.png ├── index.html ├── package.json ├── script │ ├── inherited-summary.js │ ├── inner-link.js │ ├── patch-for-local.js │ ├── prettify │ │ ├── Apache-License-2.0.txt │ │ └── prettify.js │ ├── pretty-print.js │ ├── search.js │ ├── search_index.js │ └── test-summary.js ├── source.html └── variable │ └── index.html ├── esdoc.json ├── examples └── progress-bar.html ├── package.json ├── src ├── audiomanager.js ├── effect.js ├── effectmanager.js ├── main.js └── sources │ ├── canvassource.js │ ├── imagesource.js │ ├── mediasource.js │ └── videosource.js ├── tutorial ├── 1.png ├── 2.png ├── 3.png ├── 4.png ├── 5.png ├── 6.png └── tutorial.md ├── webpack.commonjs2.config.js └── webpack.config.js /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | npm-debug.log -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "{}" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright {yyyy} {name of copyright owner} 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | 203 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # HTML5-Video-Compositor 2 | 3 | **Note: For new projects please consider using the [VideoContext](https://github.com/bbc/videocontext), a new and improved library from BBC R&D which overcomes some of the limitations of the html5-video-compositor.** 4 | 5 | A shader based video composition engine for the browser. 6 | 7 | ```HTML 8 | 9 | 10 | 11 | 12 | 13 | 28 | 29 | 30 | 31 | 32 | ``` 33 | 34 | 35 | ## Introduction 36 | This is an experimental video composition engine which can play edit decision lists in the browser. Content can be dynamically appended to the EDL as it's playing to create interactive and responsive content. 37 | 38 | In video editing terms an EDL defines the points at which to cut and assemble video sources. VideoCompositor uses a simple JSON based EDL to describe how to cut and assemble HTML5 video sources, images, and WebGL contexts, it also provides a framework for performing shader based compositing operations (i.e cross-fades, green-screen). 39 | 40 | 41 | ## Limitations 42 | 43 | * Effects are limited to one shader per track ... stay tuned for updates on this. 44 | * (Probably) won't work on mobile. 45 | * Cant' change the properties of MediaSourceReferences dynamically. 46 | * Videos must be encoded correctly. 47 | 48 | ## Video Encoding 49 | You will probably only see acceptable video performance if you encode videos with some kind of "fast decode" option. Using the avconv tool this can be done with the following command. 50 | 51 | ```Bash 52 | avconv -i input.mp4 -tune fastdecode -strict experimental output.mp4 53 | ``` 54 | 55 | 56 | ## Documentation 57 | 58 | The [tutorial.md file](https://github.com/bbc/html5-video-compositor/blob/master/tutorial/tutorial.md) in the tutorial directory of the project gives a walk-through of using the library to sequence clips and perform simple effects. 59 | 60 | [API documentation](https://github.com/bbc/html5-video-compositor/tree/master/doc) is available in the /doc directory of the project. 61 | 62 | Notes about usage, behaviors, and other miscellaneous information is available on the [Wiki](https://github.com/bbc/html5-video-compositor/wiki). 63 | 64 | 65 | ## Build 66 | 67 | This project uses npm to manage dependencies. To build the compositor, in the root of the project run: 68 | 69 | ```Bash 70 | npm install 71 | ``` 72 | 73 | Once this has completed you can build the source files by running (this will build a commonjs2 and a vanilla js file): 74 | 75 | ```Bash 76 | npm run build 77 | ``` 78 | 79 | To auto-recompile the project on source change run (this will only rebuild the vanilla js file): 80 | ```Bash 81 | npm run dev 82 | ``` 83 | -------------------------------------------------------------------------------- /doc/badge.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | document 13 | document 14 | 38% 15 | 38% 16 | 17 | 18 | -------------------------------------------------------------------------------- /doc/coverage.json: -------------------------------------------------------------------------------- 1 | { 2 | "coverage": "38.21%", 3 | "expectCount": 123, 4 | "actualCount": 47, 5 | "files": { 6 | "src/audiomanager.js": { 7 | "expectCount": 12, 8 | "actualCount": 0, 9 | "undocumentLines": [ 10 | 24, 11 | 26, 12 | 102, 13 | 28, 14 | 29, 15 | 61, 16 | 25, 17 | 32, 18 | 45, 19 | 55, 20 | 27, 21 | 67 22 | ] 23 | }, 24 | "src/sources/canvassource.js": { 25 | "expectCount": 12, 26 | "actualCount": 5, 27 | "undocumentLines": [ 28 | 4, 29 | 72, 30 | 60, 31 | 21, 32 | 51, 33 | 67, 34 | 20 35 | ] 36 | }, 37 | "src/effect.js": { 38 | "expectCount": 11, 39 | "actualCount": 0, 40 | "undocumentLines": [ 41 | 69, 42 | 70, 43 | 141, 44 | 95, 45 | 71, 46 | 121, 47 | 110, 48 | 127, 49 | 126, 50 | 131, 51 | 72 52 | ] 53 | }, 54 | "src/effectmanager.js": { 55 | "expectCount": 8, 56 | "actualCount": 0, 57 | "undocumentLines": [ 58 | 3, 59 | 4, 60 | 43, 61 | 5, 62 | 31, 63 | 6, 64 | 11, 65 | 17 66 | ] 67 | }, 68 | "src/main.js": { 69 | "expectCount": 30, 70 | "actualCount": 22, 71 | "undocumentLines": [ 72 | 1294, 73 | 1148, 74 | 1044, 75 | 30, 76 | 63, 77 | 83, 78 | 73, 79 | 1458 80 | ] 81 | }, 82 | "src/sources/imagesource.js": { 83 | "expectCount": 12, 84 | "actualCount": 5, 85 | "undocumentLines": [ 86 | 4, 87 | 59, 88 | 53, 89 | 75, 90 | 50, 91 | 70, 92 | 52 93 | ] 94 | }, 95 | "src/sources/mediasource.js": { 96 | "expectCount": 24, 97 | "actualCount": 8, 98 | "undocumentLines": [ 99 | 3, 100 | 34, 101 | 23, 102 | 27, 103 | 21, 104 | 31, 105 | 22, 106 | 227, 107 | 32, 108 | 223, 109 | 25, 110 | 26, 111 | 28, 112 | 24, 113 | 29, 114 | 30 115 | ] 116 | }, 117 | "src/sources/videosource.js": { 118 | "expectCount": 14, 119 | "actualCount": 7, 120 | "undocumentLines": [ 121 | 16, 122 | 112, 123 | 107, 124 | 105, 125 | 33, 126 | 156, 127 | 106 128 | ] 129 | } 130 | } 131 | } -------------------------------------------------------------------------------- /doc/css/prettify-tomorrow.css: -------------------------------------------------------------------------------- 1 | /* Tomorrow Theme */ 2 | /* Original theme - https://github.com/chriskempson/tomorrow-theme */ 3 | /* Pretty printing styles. Used with prettify.js. */ 4 | /* SPAN elements with the classes below are added by prettyprint. */ 5 | /* plain text */ 6 | .pln { 7 | color: #4d4d4c; } 8 | 9 | @media screen { 10 | /* string content */ 11 | .str { 12 | color: #718c00; } 13 | 14 | /* a keyword */ 15 | .kwd { 16 | color: #8959a8; } 17 | 18 | /* a comment */ 19 | .com { 20 | color: #8e908c; } 21 | 22 | /* a type name */ 23 | .typ { 24 | color: #4271ae; } 25 | 26 | /* a literal value */ 27 | .lit { 28 | color: #f5871f; } 29 | 30 | /* punctuation */ 31 | .pun { 32 | color: #4d4d4c; } 33 | 34 | /* lisp open bracket */ 35 | .opn { 36 | color: #4d4d4c; } 37 | 38 | /* lisp close bracket */ 39 | .clo { 40 | color: #4d4d4c; } 41 | 42 | /* a markup tag name */ 43 | .tag { 44 | color: #c82829; } 45 | 46 | /* a markup attribute name */ 47 | .atn { 48 | color: #f5871f; } 49 | 50 | /* a markup attribute value */ 51 | .atv { 52 | color: #3e999f; } 53 | 54 | /* a declaration */ 55 | .dec { 56 | color: #f5871f; } 57 | 58 | /* a variable name */ 59 | .var { 60 | color: #c82829; } 61 | 62 | /* a function name */ 63 | .fun { 64 | color: #4271ae; } } 65 | /* Use higher contrast and text-weight for printable form. */ 66 | @media print, projection { 67 | .str { 68 | color: #060; } 69 | 70 | .kwd { 71 | color: #006; 72 | font-weight: bold; } 73 | 74 | .com { 75 | color: #600; 76 | font-style: italic; } 77 | 78 | .typ { 79 | color: #404; 80 | font-weight: bold; } 81 | 82 | .lit { 83 | color: #044; } 84 | 85 | .pun, .opn, .clo { 86 | color: #440; } 87 | 88 | .tag { 89 | color: #006; 90 | font-weight: bold; } 91 | 92 | .atn { 93 | color: #404; } 94 | 95 | .atv { 96 | color: #060; } } 97 | /* Style */ 98 | /* 99 | pre.prettyprint { 100 | background: white; 101 | font-family: Consolas, Monaco, 'Andale Mono', monospace; 102 | font-size: 12px; 103 | line-height: 1.5; 104 | border: 1px solid #ccc; 105 | padding: 10px; } 106 | */ 107 | 108 | /* Specify class=linenums on a pre to get line numbering */ 109 | ol.linenums { 110 | margin-top: 0; 111 | margin-bottom: 0; } 112 | 113 | /* IE indents via margin-left */ 114 | li.L0, 115 | li.L1, 116 | li.L2, 117 | li.L3, 118 | li.L4, 119 | li.L5, 120 | li.L6, 121 | li.L7, 122 | li.L8, 123 | li.L9 { 124 | /* */ } 125 | 126 | /* Alternate shading for lines */ 127 | li.L1, 128 | li.L3, 129 | li.L5, 130 | li.L7, 131 | li.L9 { 132 | /* */ } 133 | -------------------------------------------------------------------------------- /doc/css/style.css: -------------------------------------------------------------------------------- 1 | @import url(https://fonts.googleapis.com/css?family=Roboto:400,300,700); 2 | 3 | * { 4 | margin: 0; 5 | padding: 0; 6 | text-decoration: none; 7 | } 8 | 9 | html 10 | { 11 | font-family: 'Roboto', sans-serif; 12 | overflow: auto; 13 | font-size: 14px; 14 | /*color: #4d4e53;*/ 15 | color: rgba(0, 0, 0, .68); 16 | background-color: #fff; 17 | } 18 | 19 | a { 20 | /*color: #0095dd;*/ 21 | /*color:rgb(37, 138, 175);*/ 22 | color: #039BE5; 23 | } 24 | 25 | code a:hover { 26 | text-decoration: underline; 27 | } 28 | 29 | ul, ol { 30 | padding-left: 20px; 31 | } 32 | 33 | ul li { 34 | list-style: disc; 35 | margin: 4px 0; 36 | } 37 | 38 | ol li { 39 | margin: 4px 0; 40 | } 41 | 42 | h1 { 43 | margin-bottom: 10px; 44 | font-size: 34px; 45 | font-weight: 300; 46 | border-bottom: solid 1px #ddd; 47 | } 48 | 49 | h2 { 50 | margin-top: 24px; 51 | margin-bottom: 10px; 52 | font-size: 20px; 53 | border-bottom: solid 1px #ddd; 54 | font-weight: 300; 55 | } 56 | 57 | h3 { 58 | position: relative; 59 | font-size: 16px; 60 | margin-bottom: 12px; 61 | background-color: #E2E2E2; 62 | padding: 4px; 63 | font-weight: 300; 64 | } 65 | 66 | del { 67 | text-decoration: line-through; 68 | } 69 | 70 | p { 71 | margin-bottom: 15px; 72 | line-height: 19px; 73 | } 74 | 75 | p > code { 76 | background-color: #f5f5f5; 77 | border-radius: 3px; 78 | } 79 | 80 | pre > code { 81 | display: block; 82 | } 83 | 84 | pre.prettyprint, pre > code { 85 | padding: 4px; 86 | margin: 1em 0; 87 | background-color: #f5f5f5; 88 | border-radius: 3px; 89 | } 90 | 91 | pre.prettyprint > code { 92 | margin: 0; 93 | } 94 | 95 | p > code, 96 | li > code { 97 | padding: 0 4px; 98 | border-radius: 3px; 99 | } 100 | 101 | .import-path pre.prettyprint, 102 | .import-path pre.prettyprint code { 103 | margin: 0; 104 | padding: 0; 105 | border: none; 106 | background: white; 107 | } 108 | 109 | .layout-container { 110 | /*display: flex;*/ 111 | /*flex-direction: row;*/ 112 | /*justify-content: flex-start;*/ 113 | /*align-items: stretch;*/ 114 | } 115 | 116 | .layout-container > header { 117 | height: 40px; 118 | line-height: 40px; 119 | font-size: 16px; 120 | padding: 0 10px; 121 | margin: 0; 122 | position: fixed; 123 | width: 100%; 124 | z-index: 1; 125 | background-color: white; 126 | top: 0; 127 | border-bottom: solid 1px #E02130; 128 | } 129 | .layout-container > header > a{ 130 | margin: 0 5px; 131 | } 132 | 133 | .layout-container > header > a.repo-url-github { 134 | font-size: 0; 135 | display: inline-block; 136 | width: 20px; 137 | height: 38px; 138 | background: url("../image/github.png") no-repeat center; 139 | background-size: 20px; 140 | vertical-align: top; 141 | } 142 | 143 | .navigation { 144 | position: fixed; 145 | top: 0; 146 | left: 0; 147 | box-sizing: border-box; 148 | width: 250px; 149 | height: 100%; 150 | padding-top: 40px; 151 | padding-left: 15px; 152 | padding-bottom: 2em; 153 | margin-top:1em; 154 | overflow-x: scroll; 155 | box-shadow: rgba(255, 255, 255, 1) -1px 0 0 inset; 156 | border-right: 1px solid rgba(0, 0, 0, 0.1); 157 | } 158 | 159 | .navigation ul { 160 | padding: 0; 161 | } 162 | 163 | .navigation li { 164 | list-style: none; 165 | margin: 4px 0; 166 | white-space: nowrap; 167 | } 168 | 169 | .navigation .nav-dir-path { 170 | margin-top: 0.7em; 171 | margin-bottom: 0.25em; 172 | font-size: 0.8em; 173 | color: #aaa; 174 | } 175 | 176 | .kind-class, 177 | .kind-interface, 178 | .kind-function, 179 | .kind-typedef, 180 | .kind-variable, 181 | .kind-external { 182 | margin-left: 0.75em; 183 | width: 1.2em; 184 | height: 1.2em; 185 | display: inline-block; 186 | text-align: center; 187 | border-radius: 0.2em; 188 | margin-right: 0.2em; 189 | font-weight: bold; 190 | } 191 | 192 | .kind-class { 193 | color: #009800; 194 | background-color: #bfe5bf; 195 | } 196 | 197 | .kind-interface { 198 | color: #fbca04; 199 | background-color: #fef2c0; 200 | } 201 | 202 | .kind-function { 203 | color: #6b0090; 204 | background-color: #d6bdde; 205 | } 206 | 207 | .kind-variable { 208 | color: #eb6420; 209 | background-color: #fad8c7; 210 | } 211 | 212 | .kind-typedef { 213 | color: #db001e; 214 | background-color: #edbec3; 215 | } 216 | 217 | .kind-external { 218 | color: #0738c3; 219 | background-color: #bbcbea; 220 | } 221 | 222 | h1 .version, 223 | h1 .url a { 224 | font-size: 14px; 225 | color: #aaa; 226 | } 227 | 228 | .content { 229 | margin-top: 40px; 230 | margin-left: 250px; 231 | padding: 10px 50px 10px 20px; 232 | } 233 | 234 | .header-notice { 235 | font-size: 14px; 236 | color: #aaa; 237 | margin: 0; 238 | } 239 | 240 | .expression-extends .prettyprint { 241 | margin-left: 10px; 242 | background: white; 243 | } 244 | 245 | .extends-chain { 246 | border-bottom: 1px solid#ddd; 247 | padding-bottom: 10px; 248 | margin-bottom: 10px; 249 | } 250 | 251 | .extends-chain span:nth-of-type(1) { 252 | padding-left: 10px; 253 | } 254 | 255 | .extends-chain > div { 256 | margin: 5px 0; 257 | } 258 | 259 | .description table { 260 | font-size: 14px; 261 | border-spacing: 0; 262 | border: 0; 263 | border-collapse: collapse; 264 | } 265 | 266 | .description thead { 267 | background: #999; 268 | color: white; 269 | } 270 | 271 | .description table td, 272 | .description table th { 273 | border: solid 1px #ddd; 274 | padding: 4px; 275 | font-weight: normal; 276 | } 277 | 278 | .flat-list ul { 279 | padding-left: 0; 280 | } 281 | 282 | .flat-list li { 283 | display: inline; 284 | list-style: none; 285 | } 286 | 287 | table.summary { 288 | width: 100%; 289 | margin: 10px 0; 290 | border-spacing: 0; 291 | border: 0; 292 | border-collapse: collapse; 293 | } 294 | 295 | table.summary thead { 296 | background: #999; 297 | color: white; 298 | } 299 | 300 | table.summary td { 301 | border: solid 1px #ddd; 302 | padding: 4px 10px; 303 | } 304 | 305 | table.summary tbody td:nth-child(1) { 306 | text-align: right; 307 | white-space: nowrap; 308 | min-width: 64px; 309 | vertical-align: top; 310 | } 311 | 312 | table.summary tbody td:nth-child(2) { 313 | width: 100%; 314 | border-right: none; 315 | } 316 | 317 | table.summary tbody td:nth-child(3) { 318 | white-space: nowrap; 319 | border-left: none; 320 | vertical-align: top; 321 | } 322 | 323 | table.summary td > div:nth-of-type(2) { 324 | padding-top: 4px; 325 | padding-left: 15px; 326 | } 327 | 328 | table.summary td p { 329 | margin-bottom: 0; 330 | } 331 | 332 | .inherited-summary thead td { 333 | padding-left: 2px; 334 | } 335 | 336 | .inherited-summary thead a { 337 | color: white; 338 | } 339 | 340 | .inherited-summary .summary tbody { 341 | display: none; 342 | } 343 | 344 | .inherited-summary .summary .toggle { 345 | padding: 0 4px; 346 | font-size: 12px; 347 | cursor: pointer; 348 | } 349 | .inherited-summary .summary .toggle.closed:before { 350 | content: "▶"; 351 | } 352 | .inherited-summary .summary .toggle.opened:before { 353 | content: "▼"; 354 | } 355 | 356 | .member, .method { 357 | margin-bottom: 24px; 358 | } 359 | 360 | table.params { 361 | width: 100%; 362 | margin: 10px 0; 363 | border-spacing: 0; 364 | border: 0; 365 | border-collapse: collapse; 366 | } 367 | 368 | table.params thead { 369 | background: #eee; 370 | color: #aaa; 371 | } 372 | 373 | table.params td { 374 | padding: 4px; 375 | border: solid 1px #ddd; 376 | } 377 | 378 | table.params td p { 379 | margin: 0; 380 | } 381 | 382 | .content .detail > * { 383 | margin: 15px 0; 384 | } 385 | 386 | .content .detail > h3 { 387 | color: black; 388 | } 389 | 390 | .content .detail > div { 391 | margin-left: 10px; 392 | } 393 | 394 | .content .detail > .import-path { 395 | margin-top: -8px; 396 | } 397 | 398 | .content .detail + .detail { 399 | margin-top: 30px; 400 | } 401 | 402 | .content .detail .throw td:first-child { 403 | padding-right: 10px; 404 | } 405 | 406 | .content .detail h4 + :not(pre) { 407 | padding-left: 0; 408 | margin-left: 10px; 409 | } 410 | 411 | .content .detail h4 + ul li { 412 | list-style: none; 413 | } 414 | 415 | .return-param * { 416 | display: inline; 417 | } 418 | 419 | .argument-params { 420 | margin-bottom: 20px; 421 | } 422 | 423 | .return-type { 424 | padding-right: 10px; 425 | font-weight: normal; 426 | } 427 | 428 | .return-desc { 429 | margin-left: 10px; 430 | margin-top: 4px; 431 | } 432 | 433 | .return-desc p { 434 | margin: 0; 435 | } 436 | 437 | .deprecated, .experimental, .instance-docs { 438 | border-left: solid 5px orange; 439 | padding-left: 4px; 440 | margin: 4px 0; 441 | } 442 | 443 | tr.listen p, 444 | tr.throw p, 445 | tr.emit p{ 446 | margin-bottom: 10px; 447 | } 448 | 449 | .version, .since { 450 | color: #aaa; 451 | } 452 | 453 | h3 .right-info { 454 | position: absolute; 455 | right: 4px; 456 | font-size: 14px; 457 | } 458 | 459 | .version + .since:before { 460 | content: '| '; 461 | } 462 | 463 | .see { 464 | margin-top: 10px; 465 | } 466 | 467 | .see h4 { 468 | margin: 4px 0; 469 | } 470 | 471 | .content .detail h4 + .example-doc { 472 | margin: 6px 0; 473 | } 474 | 475 | .example-caption { 476 | position: relative; 477 | bottom: -1px; 478 | display: inline-block; 479 | padding: 4px; 480 | font-style: italic; 481 | background-color: #f5f5f5; 482 | font-weight: bold; 483 | border-radius: 3px; 484 | border-bottom-left-radius: 0; 485 | border-bottom-right-radius: 0; 486 | } 487 | 488 | .example-caption + pre.source-code { 489 | margin-top: 0; 490 | border-top-left-radius: 0; 491 | } 492 | 493 | footer, .file-footer { 494 | text-align: right; 495 | font-style: italic; 496 | font-weight: 100; 497 | font-size: 13px; 498 | margin-right: 50px; 499 | margin-left: 270px; 500 | border-top: 1px solid #ddd; 501 | padding-top: 30px; 502 | margin-top: 20px; 503 | padding-bottom: 10px; 504 | } 505 | 506 | pre.source-code { 507 | background: #f5f5f5; 508 | padding: 4px; 509 | } 510 | 511 | pre.raw-source-code > code { 512 | padding: 0; 513 | margin: 0; 514 | } 515 | 516 | pre.source-code.line-number { 517 | padding: 0; 518 | } 519 | 520 | pre.source-code ol { 521 | background: #eee; 522 | padding-left: 40px; 523 | } 524 | 525 | pre.source-code li { 526 | background: white; 527 | padding-left: 4px; 528 | list-style: decimal; 529 | margin: 0; 530 | } 531 | 532 | pre.source-code.line-number li.active { 533 | background: rgb(255, 255, 150); 534 | } 535 | 536 | pre.source-code.line-number li.error-line { 537 | background: #ffb8bf; 538 | } 539 | 540 | table.files-summary { 541 | width: 100%; 542 | margin: 10px 0; 543 | border-spacing: 0; 544 | border: 0; 545 | border-collapse: collapse; 546 | text-align: right; 547 | } 548 | 549 | table.files-summary tbody tr:hover { 550 | background: #eee; 551 | } 552 | 553 | table.files-summary td:first-child, 554 | table.files-summary td:nth-of-type(2) { 555 | text-align: left; 556 | } 557 | 558 | table.files-summary[data-use-coverage="false"] td.coverage { 559 | display: none; 560 | } 561 | 562 | table.files-summary thead { 563 | background: #999; 564 | color: white; 565 | } 566 | 567 | table.files-summary td { 568 | border: solid 1px #ddd; 569 | padding: 4px 10px; 570 | vertical-align: top; 571 | } 572 | 573 | table.files-summary td.identifiers > span { 574 | display: block; 575 | margin-top: 4px; 576 | } 577 | table.files-summary td.identifiers > span:first-child { 578 | margin-top: 0; 579 | } 580 | 581 | table.files-summary .coverage-count { 582 | font-size: 12px; 583 | color: #aaa; 584 | display: inline-block; 585 | min-width: 40px; 586 | } 587 | 588 | .total-coverage-count { 589 | position: relative; 590 | bottom: 2px; 591 | font-size: 12px; 592 | color: #666; 593 | font-weight: 500; 594 | padding-left: 5px; 595 | } 596 | 597 | table.test-summary thead { 598 | background: #999; 599 | color: white; 600 | } 601 | 602 | table.test-summary thead .test-description { 603 | width: 50%; 604 | } 605 | 606 | table.test-summary { 607 | width: 100%; 608 | margin: 10px 0; 609 | border-spacing: 0; 610 | border: 0; 611 | border-collapse: collapse; 612 | } 613 | 614 | table.test-summary thead .test-count { 615 | width: 3em; 616 | } 617 | 618 | table.test-summary tbody tr:hover { 619 | background-color: #eee; 620 | } 621 | 622 | table.test-summary td { 623 | border: solid 1px #ddd; 624 | padding: 4px 10px; 625 | vertical-align: top; 626 | } 627 | 628 | table.test-summary td p { 629 | margin: 0; 630 | } 631 | 632 | table.test-summary tr.test-describe .toggle { 633 | display: inline-block; 634 | float: left; 635 | margin-right: 4px; 636 | cursor: pointer; 637 | } 638 | 639 | table.test-summary tr.test-describe .toggle.opened:before { 640 | content: '▼'; 641 | } 642 | 643 | table.test-summary tr.test-describe .toggle.closed:before { 644 | content: '▶'; 645 | } 646 | 647 | table.test-summary .test-target > span { 648 | display: block; 649 | margin-top: 4px; 650 | } 651 | table.test-summary .test-target > span:first-child { 652 | margin-top: 0; 653 | } 654 | 655 | .inner-link-active { 656 | background: rgb(255, 255, 150); 657 | } 658 | 659 | /* search box */ 660 | .search-box { 661 | position: absolute; 662 | top: 10px; 663 | right: 50px; 664 | padding-right: 8px; 665 | padding-bottom: 10px; 666 | line-height: normal; 667 | font-size: 12px; 668 | } 669 | 670 | .search-box img { 671 | width: 20px; 672 | vertical-align: top; 673 | } 674 | 675 | .search-input { 676 | display: inline; 677 | visibility: hidden; 678 | width: 0; 679 | padding: 2px; 680 | height: 1.5em; 681 | outline: none; 682 | background: transparent; 683 | border: 1px #0af; 684 | border-style: none none solid none; 685 | vertical-align: bottom; 686 | } 687 | 688 | .search-input-edge { 689 | display: none; 690 | width: 1px; 691 | height: 5px; 692 | background-color: #0af; 693 | vertical-align: bottom; 694 | } 695 | 696 | .search-result { 697 | position: absolute; 698 | display: none; 699 | height: 600px; 700 | width: 100%; 701 | padding: 0; 702 | margin-top: 5px; 703 | margin-left: 24px; 704 | background: white; 705 | box-shadow: 1px 1px 4px rgb(0,0,0); 706 | white-space: nowrap; 707 | overflow-y: scroll; 708 | } 709 | 710 | .search-result-import-path { 711 | color: #aaa; 712 | font-size: 12px; 713 | } 714 | 715 | .search-result li { 716 | list-style: none; 717 | padding: 2px 4px; 718 | } 719 | 720 | .search-result li a { 721 | display: block; 722 | } 723 | 724 | .search-result li.selected { 725 | background: #ddd; 726 | } 727 | 728 | .search-result li.search-separator { 729 | background: rgb(37, 138, 175); 730 | color: white; 731 | } 732 | 733 | .search-box.active .search-input { 734 | visibility: visible; 735 | transition: width 0.2s ease-out; 736 | width: 300px; 737 | } 738 | 739 | .search-box.active .search-input-edge { 740 | display: inline-block; 741 | } 742 | 743 | /* coverage badge */ 744 | .esdoc-coverage { 745 | display: inline-block; 746 | height: 20px; 747 | vertical-align: top; 748 | } 749 | 750 | h1 .esdoc-coverage { 751 | position: relative; 752 | top: -4px; 753 | } 754 | 755 | .esdoc-coverage-wrap { 756 | color: white; 757 | font-size: 12px; 758 | font-weight: 500; 759 | } 760 | 761 | .esdoc-coverage-label { 762 | padding: 3px 4px 3px 6px; 763 | background: linear-gradient(to bottom, #5e5e5e 0%,#4c4c4c 100%); 764 | border-radius: 4px 0 0 4px; 765 | display: inline-block; 766 | height: 20px; 767 | box-sizing: border-box; 768 | line-height: 14px; 769 | } 770 | 771 | .esdoc-coverage-ratio { 772 | padding: 3px 6px 3px 4px; 773 | border-radius: 0 4px 4px 0; 774 | display: inline-block; 775 | height: 20px; 776 | box-sizing: border-box; 777 | line-height: 14px; 778 | } 779 | 780 | .esdoc-coverage-low { 781 | background: linear-gradient(to bottom, #db654f 0%,#c9533d 100%); 782 | } 783 | 784 | .esdoc-coverage-middle { 785 | background: linear-gradient(to bottom, #dab226 0%,#c9a179 100%); 786 | } 787 | 788 | .esdoc-coverage-high { 789 | background: linear-gradient(to bottom, #4fc921 0%,#3eb810 100%); 790 | } 791 | 792 | /* github markdown */ 793 | .github-markdown { 794 | font-size: 16px; 795 | } 796 | 797 | .github-markdown h1, 798 | .github-markdown h2, 799 | .github-markdown h3, 800 | .github-markdown h4, 801 | .github-markdown h5 { 802 | margin-top: 1em; 803 | margin-bottom: 16px; 804 | font-weight: bold; 805 | padding: 0; 806 | } 807 | 808 | .github-markdown h1:nth-of-type(1) { 809 | margin-top: 0; 810 | } 811 | 812 | .github-markdown h1 { 813 | font-size: 2em; 814 | padding-bottom: 0.3em; 815 | } 816 | 817 | .github-markdown h2 { 818 | font-size: 1.75em; 819 | padding-bottom: 0.3em; 820 | } 821 | 822 | .github-markdown h3 { 823 | font-size: 1.5em; 824 | background-color: transparent; 825 | } 826 | 827 | .github-markdown h4 { 828 | font-size: 1.25em; 829 | } 830 | 831 | .github-markdown h5 { 832 | font-size: 1em; 833 | } 834 | 835 | .github-markdown ul, .github-markdown ol { 836 | padding-left: 2em; 837 | } 838 | 839 | .github-markdown pre > code { 840 | font-size: 0.85em; 841 | } 842 | 843 | .github-markdown table { 844 | margin-bottom: 1em; 845 | border-collapse: collapse; 846 | border-spacing: 0; 847 | } 848 | 849 | .github-markdown table tr { 850 | background-color: #fff; 851 | border-top: 1px solid #ccc; 852 | } 853 | 854 | .github-markdown table th, 855 | .github-markdown table td { 856 | padding: 6px 13px; 857 | border: 1px solid #ddd; 858 | } 859 | 860 | .github-markdown table tr:nth-child(2n) { 861 | background-color: #f8f8f8; 862 | } 863 | -------------------------------------------------------------------------------- /doc/file/src/audiomanager.js.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | src/audiomanager.js | HTML5 Video Compositor API Document 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 |
16 | Home 17 | Identifier 18 | Source 19 | 20 | 21 | 28 |
29 | 30 | 52 | 53 |

src/audiomanager.js

54 |

 55 | 
 56 | function isIdInTrack(id, track){
 57 |     for (let i = 0; i < track.length; i++) {
 58 |         if (track[i].id === id){
 59 |             return true;
 60 |         }
 61 |     }
 62 |     return false;
 63 | }
 64 | 
 65 | function getTrackIndexsForId(id, tracks){
 66 |     let trackIndexs = [];
 67 |     for (let i = 0; i < tracks.length; i++) {
 68 |         let track = tracks[i];
 69 |         if (isIdInTrack(id, track)){
 70 |             trackIndexs.push(i);
 71 |         }
 72 |     }
 73 |     return trackIndexs;
 74 | }
 75 | 
 76 | 
 77 | class AudioManager {
 78 |     constructor(audioCtx){
 79 |         this.audioCtx = audioCtx;
 80 |         this.tracks = [];
 81 |         this.audioNodes = new Map();
 82 |         this.audioOutputNodes = [];
 83 |     }
 84 |         
 85 |     createAudioNodeFromTrack(track){
 86 |         if (this.audioCtx === undefined){
 87 |             // There can only be a max of 6 AudioContexts in most browsers, so only instantiate it here rather than in 
 88 |             // constructor as it's genuinley needed. Otherwise having >6 VideoCompositor instances running will break 
 89 |             // the browser.
 90 |             this.audioCtx = new AudioContext();
 91 |         }
 92 |         this.tracks.push(track);
 93 |         let trackBus = this.audioCtx.createGain();
 94 |         this.audioOutputNodes.push(trackBus);
 95 |         return trackBus;
 96 |     }
 97 | 
 98 |     getAudioContext(){
 99 |         if (this.audioCtx === undefined){
100 |             // There can only be a max of 6 AudioContexts in most browsers, so only instantiate it here rather than in 
101 |             // constructor as it's genuinley needed. Otherwise having >6 VideoCompositor instances running will break 
102 |             // the browser.
103 |             this.audioCtx = new AudioContext();
104 |         }
105 |         return this.audioCtx;
106 |     }
107 | 
108 |     removeFromCacheById(id){
109 |         let node = this.audioNodes.get(id);
110 |         node.disconnect();
111 |         this.audioNodes.delete(id);
112 |     }
113 | 
114 |     clearAudioNodeCache(){
115 |         for (let id of this.audioNodes.keys()){
116 |             this.removeFromCacheById(id);
117 |         }
118 |     }
119 | 
120 |     update(mediaSources, currentlyPlaying){
121 |         let currentlyPlayingIds = [];
122 |         for (let i = 0; i < currentlyPlaying.length; i++) {
123 |             let mediaSourceRef = currentlyPlaying[i];
124 |             currentlyPlayingIds.push(mediaSourceRef.id);
125 |         }
126 | 
127 |         if (mediaSources === undefined) return;
128 |         for (let id of mediaSources.keys()) {
129 |             let mediaSource = mediaSources.get(id);
130 |             let trackIndexs = getTrackIndexsForId(id, this.tracks);
131 |             if (trackIndexs.length ===0){
132 |                 continue; //No mappings for this id
133 |             }    
134 |             if (!this.audioNodes.has(id)){
135 |                 //if an AudioNode for this id does not exist, create it.
136 |                 let audioNode;
137 |                 try{
138 |                     audioNode = this.audioCtx.createMediaElementSource(mediaSource.element);
139 |                 } catch (err) {
140 |                     continue;
141 |                 }
142 | 
143 |                 this.audioNodes.set(id, audioNode);
144 |                 //make the connections from the audio node to the appropriate output tracks
145 |                 for (let i = 0; i < trackIndexs.length; i++) {
146 |                     let trackIndex = trackIndexs[i];
147 |                     audioNode.connect(this.audioOutputNodes[trackIndex]);
148 |                 }
149 |             }else {
150 |             }
151 |         }
152 |         //TODO add test to make sure all id's for audio nodes stored in this.audioNodes exist in the current mediaSources, otherwise delete them.
153 |     }
154 | }
155 | export default AudioManager;
156 | 
157 | 
158 | 159 |
160 | 161 | 164 | 165 | 166 | 167 | 168 | 169 | 170 | 171 | 172 | 173 | 174 | -------------------------------------------------------------------------------- /doc/file/src/effect.js.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | src/effect.js | HTML5 Video Compositor API Document 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 |
16 | Home 17 | Identifier 18 | Source 19 | 20 | 21 | 28 |
29 | 30 | 52 | 53 |

src/effect.js

54 |
function refreshTextures(playlistEffectObject, textures, gl){
 55 |     let textureOffset = 1;
 56 | 
 57 |     if (playlistEffectObject.parameters === undefined) return;
 58 | 
 59 |     let parameterKeys = Object.keys(playlistEffectObject.parameters);
 60 |     for (let i = 0; i < parameterKeys.length; i++) {
 61 |         let key = parameterKeys[i];
 62 |         let parameter = playlistEffectObject.parameters[key];
 63 |         if (typeof parameter !== "number"){
 64 |             let texture = textures[textureOffset-1];
 65 |             gl.activeTexture(gl.TEXTURE0 + textureOffset);
 66 |             gl.bindTexture(gl.TEXTURE_2D, texture);
 67 |             gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
 68 |             gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
 69 |             gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST);
 70 |             gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST);
 71 |             gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, parameter);
 72 |             textureOffset += 1;
 73 |         }  
 74 |     }
 75 | }
 76 | 
 77 | function loadTextures(playlistEffectObject, gl){
 78 |     if (playlistEffectObject.parameters === undefined) return [];
 79 |     let parameterKeys = Object.keys(playlistEffectObject.parameters);
 80 |     let textures = [];
 81 |     for (let i = 0; i < parameterKeys.length; i++) {
 82 |         let key = parameterKeys[i];
 83 |         let parameter = playlistEffectObject.parameters[key];
 84 |         if (typeof parameter !== "number"){
 85 |             let texture = gl.createTexture();
 86 |             textures.push(texture);                
 87 |         }
 88 |     }
 89 |     refreshTextures(playlistEffectObject, textures, gl);
 90 |     return textures;
 91 | }
 92 | 
 93 | 
 94 | function compileShader(gl, shaderSource, shaderType) {
 95 |     let shader = gl.createShader(shaderType);
 96 |     gl.shaderSource(shader, shaderSource);
 97 |     gl.compileShader(shader);
 98 |     let success = gl.getShaderParameter(shader, gl.COMPILE_STATUS);
 99 |     if (!success) {
100 |         throw "could not compile shader:" + gl.getShaderInfoLog(shader);
101 |     }
102 |     return shader;
103 | }
104 | 
105 | 
106 | function createShaderProgram(gl, vertexShaderSource, fragmentShaderSource){
107 |     let vertexShader = compileShader(gl, vertexShaderSource, gl.VERTEX_SHADER);
108 |     let fragmentShader = compileShader(gl, fragmentShaderSource, gl.FRAGMENT_SHADER);
109 |     let program = gl.createProgram();
110 | 
111 |     gl.attachShader(program, vertexShader);
112 |     gl.attachShader(program, fragmentShader);
113 |     gl.linkProgram(program);
114 |    
115 |     if (!gl.getProgramParameter(program, gl.LINK_STATUS)){
116 |         throw {"error":4,"msg":"Can't link shader program for track", toString:function(){return this.msg;}};
117 |     }
118 |     return program;
119 | }
120 | 
121 | 
122 | class Effect {
123 |     constructor(playlistEffectObject, gl){
124 |         this.gl = gl;
125 |         this.vertexShaderSrc = playlistEffectObject.effect.vertexShader;
126 |         if (this.vertexShaderSrc === undefined){
127 |             this.vertexShaderSrc = "\
128 |                 uniform float progress;\
129 |                 uniform float duration;\
130 |                 uniform vec2 source_resolution;\
131 |                 uniform vec2 output_resolution;\
132 |                 attribute vec2 a_position;\
133 |                 attribute vec2 a_texCoord;\
134 |                 varying vec2 v_texCoord;\
135 |                 varying float v_progress;\
136 |                 varying float v_duration;\
137 |                 varying vec2 v_source_resolution;\
138 |                 varying vec2 v_output_resolution;\
139 |                 void main() {\
140 |                     v_progress = progress;\
141 |                     v_duration = duration;\
142 |                     v_source_resolution = source_resolution;\
143 |                     v_output_resolution = output_resolution;\
144 |                     gl_Position = vec4(vec2(2.0,2.0)*a_position-vec2(1.0, 1.0), 0.0, 1.0);\
145 |                     v_texCoord = a_texCoord;\
146 |                 }";
147 |         }
148 |         this.fragmentShaderSrc = playlistEffectObject.effect.fragmentShader;
149 |         if (this.fragmentShaderSrc === undefined){
150 |             this.fragmentShaderSrc = "\
151 |             precision mediump float;\
152 |             uniform sampler2D u_image;\
153 |             varying vec2 v_texCoord;\
154 |             varying float v_progress;\
155 |             varying float v_duration;\
156 |             varying vec2 v_source_resolution;\
157 |             varying vec2 v_output_resolution;\
158 |             void main(){\
159 |                 gl_FragColor = texture2D(u_image, v_texCoord);\
160 |             }";
161 |         }
162 | 
163 |         this.parameters = playlistEffectObject.parameters;
164 |         if (this.parameters === undefined){
165 |             this.parameters = {};
166 |         }
167 |         if (playlistEffectObject.effect.defaultParameters !== undefined){
168 |             for (let key in playlistEffectObject.effect.defaultParameters) {
169 |                 if (this.parameters[key]===undefined){
170 |                     this.parameters[key] = playlistEffectObject.effect.defaultParameters[key];
171 |                 }
172 |             }
173 |         }
174 |         this.inputs = playlistEffectObject.inputs;
175 |         if (this.inputs === undefined){
176 |             this.inputs = [];
177 |         }
178 | 
179 |         this.textures = loadTextures(playlistEffectObject, this.gl);
180 |         this.program = createShaderProgram(this.gl, this.vertexShaderSrc, this.fragmentShaderSrc);
181 |     }
182 | 
183 | 
184 |     update(playlistEffectObject){
185 |         refreshTextures(playlistEffectObject, this.textures, this.gl);
186 |         this.inputs = playlistEffectObject.inputs;
187 |         if (this.inputs === undefined){
188 |             this.inputs = [];
189 |         }
190 |     }
191 | 
192 | }
193 | 
194 | export default Effect;
195 | 196 |
197 | 198 | 201 | 202 | 203 | 204 | 205 | 206 | 207 | 208 | 209 | 210 | 211 | -------------------------------------------------------------------------------- /doc/file/src/effectmanager.js.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | src/effectmanager.js | HTML5 Video Compositor API Document 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 |
16 | Home 17 | Identifier 18 | Source 19 | 20 | 21 | 28 |
29 | 30 | 52 | 53 |

src/effectmanager.js

54 |
import Effect from "./effect.js";
 55 | 
 56 | class EffectManager {
 57 |     constructor(gl){
 58 |         this.effects = new Map();
 59 |         this.gl = gl;
 60 |         //Setup the default effect
 61 |         this.newEffect("default",{"effect":{}});
 62 |     }
 63 |     
 64 |     newEffect(id, playlistEffectObject){
 65 |         //The playlist effect object is the representation of the effect stored in the playlist object
 66 |         let effect = new Effect(playlistEffectObject, this.gl);
 67 |         this.effects.set(id, effect);
 68 |     }
 69 | 
 70 |     updateEffects(playlistEffectObjects){
 71 |         if (playlistEffectObjects === undefined) return;
 72 |         for (let key in playlistEffectObjects){
 73 |             if (this.effects.has(key)){
 74 |                 //udpate the effect
 75 |                 this.effects.get(key).update(playlistEffectObjects[key]);
 76 |             } else {
 77 |                 //create the effect
 78 |                 this.newEffect(key, playlistEffectObjects[key]);
 79 |             }
 80 |         }
 81 |         //TODO clean-up effects that don't exist
 82 |     }
 83 | 
 84 |     getEffectForInputId(inputId){
 85 |         let effectIdList = this.effects.keys();
 86 |         for (let key of effectIdList) {
 87 |             var effect = this.effects.get(key);
 88 |             if (effect.inputs.indexOf(inputId) > -1){
 89 |                 return effect;
 90 |             }
 91 |         }
 92 |         return this.effects.get("default");
 93 |     }
 94 | }
 95 | 
 96 | export default EffectManager;
 97 | 
98 | 99 |
100 | 101 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | -------------------------------------------------------------------------------- /doc/file/src/sources/canvassource.js.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | src/sources/canvassource.js | HTML5 Video Compositor API Document 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 |
16 | Home 17 | Identifier 18 | Source 19 | 20 | 21 | 28 |
29 | 30 | 52 | 53 |

src/sources/canvassource.js

54 |
//Matthew Shotton, R&D User Experince,© BBC 2015
 55 | import MediaSource from "./mediasource";
 56 | 
 57 | class CanvasSource extends MediaSource{
 58 |     /**
 59 |     * Canvas playback source. Inherits from MediaSource 
 60 |     *
 61 |     * A CanvasSource is the manifestation of a mediaSourceReference from a playlist object which has type "canvas". 
 62 |     * 
 63 |     * A CanvasSource exists for a period slightly before a CanvasSource is to play in order to give it time to preload and
 64 |     * is destroyed as soon as the CanvasSource has finished playing.
 65 |     *
 66 |     * @param {Object} properties - An object with the following attributes: id, duration, start, and element. 
 67 |     * Where src is the URL of a video, or element is a DOM Video element.
 68 |     * 
 69 |     * @param {WebGLContext} gl - a webGl context to render too.
 70 |     */
 71 |     constructor(properties, gl){
 72 |         super(properties, gl);
 73 |         this.width = properties.width;
 74 |         this.height = properties.height;
 75 |     }
 76 |     /**
 77 |     * Set the CanvasSource playing.
 78 |     */
 79 |     play(){
 80 |         super.play();
 81 |     }
 82 |     /**
 83 |     * Seek to playlist time and do something appropriate with this CavnasSource. This can effect shaders applied to this 
 84 |     * canvas and any MediaSourceListeners listening to the Id of this source.
 85 |     * @param {number} seekTime - The time to seek too, this is the overall time for the whole playlist.
 86 |     */
 87 |     seek(time){
 88 |         super.seek(time);
 89 |     }
 90 |     /**
 91 |     * Pause the CanvasSource if it is playing.
 92 |     */
 93 |     pause(){
 94 |         super.pause();
 95 |     }
 96 |     /**
 97 |     * Set the CanvasSource loading, when it's ready isReady() will return true.
 98 |     */
 99 |     load(){
100 |         //check if we're using an already instatiated element, if so don't do anything.
101 |         if (super.load()){
102 |             this.seek(0);
103 |             this.gl.texImage2D(this.gl.TEXTURE_2D, 0, this.gl.RGBA, this.gl.RGBA, this.gl.UNSIGNED_BYTE, this.element);
104 |             this.ready = true;
105 |             this.width = this.element.width;
106 |             this.height = this.element.height;
107 |             this.onready(this);
108 |             return;
109 |         }
110 | 
111 | 
112 |         //otherwise begin the loading process for this mediaSource
113 |         this.element = document.createElement("canvas");
114 |         this.element.width = this.width;
115 |         this.element.height = this.height;
116 |         this.gl.texImage2D(this.gl.TEXTURE_2D, 0, this.gl.RGBA, this.gl.RGBA, this.gl.UNSIGNED_BYTE, this.element);
117 |         this.ready = true;
118 |         this.onready(this);
119 |     }
120 |     render(program,  renderParameters, textures){
121 |         super.render(program,  renderParameters, textures);
122 |     }
123 | }
124 | 
125 | export default CanvasSource;
126 | 127 |
128 | 129 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | -------------------------------------------------------------------------------- /doc/file/src/sources/imagesource.js.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | src/sources/imagesource.js | HTML5 Video Compositor API Document 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 |
16 | Home 17 | Identifier 18 | Source 19 | 20 | 21 | 28 |
29 | 30 | 52 | 53 |

src/sources/imagesource.js

54 |
//Matthew Shotton, R&D User Experince,© BBC 2015
 55 | import MediaSource from "./mediasource";
 56 | 
 57 | class ImageSource extends MediaSource{
 58 |     /**
 59 |     * Image playback source. Inherits from MediaSource 
 60 |     *
 61 |     * A ImageSource is the manifestation of a mediaSourceReference from a playlist object which has type "image". 
 62 |     * 
 63 |     * A ImageSource exists for a period slightly before a ImageSource is to play in order to give it time to preload and
 64 |     * is destroyed as soon as the ImageSource has finished playing.
 65 |     *
 66 |     * @param {Object} properties - An object with the following attributes: id, duration, start, and src or element. 
 67 |     * Where src is the URL of a video, or element is a DOM Video element.
 68 |     * 
 69 |     * @param {WebGLContext} gl - a webGl context to render too.
 70 |     */
 71 |     constructor(properties, gl){
 72 |         super(properties, gl);
 73 |     }
 74 |     /**
 75 |     * Set the ImageSource playing.
 76 |     */
 77 |     play(){
 78 |         super.play();
 79 |     }
 80 |     /**
 81 |     * Seek to playlist time and do something appropriate with this ImageSource. This has little effect on the image as it's 
 82 |     * static but can affect any effect shaders applied to this image and any MediaSourceListeners listening to the Id of 
 83 |     * this source.
 84 |     * @param {number} seekTime - The time to seek too, this is the overall time for the whole playlist.
 85 |     */
 86 |     seek(time){
 87 |         super.seek(time);
 88 |     }
 89 |     /**
 90 |     * Pause the ImageSource if it is playing.
 91 |     */
 92 |     pause(){
 93 |         super.pause();
 94 |     }
 95 |     /**
 96 |     * Set the ImageSource loading, when it's ready isReady() will return true.
 97 |     */
 98 |     load(){
 99 |         //check if we're using an already instatiated element, if so don't do anything.
100 |         if (super.load()){
101 |             this.seek(0);
102 |             this.gl.texImage2D(this.gl.TEXTURE_2D, 0, this.gl.RGBA, this.gl.RGBA, this.gl.UNSIGNED_BYTE, this.element);
103 |             this.ready = true;
104 |             // Upload the image into the texture.
105 |             this.width = this.element.width;
106 |             this.height = this.element.height;
107 |             this.onready(this);
108 |             return;
109 |         }
110 | 
111 |         //otherwise begin the loading process for this mediaSource
112 |         this.element = new Image();
113 |         let _this = this;
114 |         this.element.onload = function(){
115 |             _this.gl.texImage2D(_this.gl.TEXTURE_2D, 0, _this.gl.RGBA, _this.gl.RGBA, _this.gl.UNSIGNED_BYTE, _this.element);
116 |             _this.ready = true;
117 |             _this.onready(_this);
118 |             _this.width = _this.element.width;
119 |             _this.height = _this.element.height;
120 |         };
121 |         this.element.src = this.src;
122 |     }
123 |     render(program,  renderParameters, textures){
124 |         super.render(program,  renderParameters, textures);
125 |     }
126 | }
127 | 
128 | export default ImageSource;
129 | 130 |
131 | 132 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | -------------------------------------------------------------------------------- /doc/file/src/sources/mediasource.js.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | src/sources/mediasource.js | HTML5 Video Compositor API Document 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 |
16 | Home 17 | Identifier 18 | Source 19 | 20 | 21 | 28 |
29 | 30 | 52 | 53 |

src/sources/mediasource.js

54 |
//Matthew Shotton, R&D User Experince,© BBC 2015
 55 | 
 56 | class MediaSource {
 57 |     /**
 58 |     * Parent class of all MediaSources 
 59 |     *
 60 |     * A MediaSource is the manifestation of a mediaSourceReference from a playlist object. It typically contains the 
 61 |     * original DOM element to be composited along with a number of functions to load, play, pause, seek and render that 
 62 |     * element to the webgl context.
 63 |     * 
 64 |     * A MediaSource exists for a period slightly before a MediaSource is to play in order to give it time to preload and
 65 |     * is destroyed as soon as the MediaSource has finished playing.
 66 |     *
 67 |     * @param {Object} properties - An object with the following attributes: id, duration, start, and src or element. 
 68 |     * Where src is the URL of something that can be used to create a DOM element that can be rendered to canvas, or 
 69 |     * element is a DOM element that can be rendered to a canvas.
 70 |     * 
 71 |     * @param {WebGLContext} gl - a webGl context to render too.
 72 |     */
 73 |     constructor(properties, gl){
 74 |         this.gl = gl;
 75 |         this.id = properties.id;
 76 |         this.duration = properties.duration;
 77 |         this.start = properties.start;
 78 |         this.playing = false;
 79 |         this.ready = false;
 80 |         this.element = undefined;
 81 |         this.src = undefined;
 82 |         this.texture = undefined;
 83 |         this.width = undefined;
 84 |         this.height = undefined;
 85 |         this.mediaSourceListeners = [];
 86 | 
 87 |         this.disposeOfElementOnDestroy = false;
 88 | 
 89 |         //If the mediaSource is created from a src string then it must be resonsible for cleaning itself up.
 90 |         if (properties.src !== undefined){
 91 |             this.disposeOfElementOnDestroy = true;
 92 |             this.src = properties.src;
 93 |         }else {
 94 |             //If the MediaSource is created from an element then it should not clean the element up on destruction as it may be used elsewhere.
 95 |             this.disposeOfElementOnDestroy = false;
 96 |             this.element = properties.element;
 97 |         }
 98 | 
 99 | 
100 |         /*let positionLocation = gl.getAttribLocation(program, "a_position");
101 |         let texCoordLocation = gl.getAttribLocation(program, "a_texCoord");*/
102 |         
103 |         //Hard Code these for now, but this is baaaaaad
104 |         let positionLocation = 0;
105 |         let texCoordLocation = 1;
106 |         
107 |         gl.blendFunc(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA);
108 |         gl.enable ( gl.BLEND) ;
109 |         // Create a texture.
110 |         this.texture = gl.createTexture();
111 |         gl.bindTexture(gl.TEXTURE_2D, this.texture);
112 |         gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, true);
113 |         // Set the parameters so we can render any size image.
114 |         gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
115 |         gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
116 |         gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST);
117 |         gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST);
118 |         
119 | 
120 |         let buffer = gl.createBuffer();
121 |         gl.bindBuffer(gl.ARRAY_BUFFER, buffer);
122 |         gl.enableVertexAttribArray(positionLocation);
123 |         gl.vertexAttribPointer(positionLocation, 2, gl.FLOAT, false, 0, 0);
124 | 
125 |         gl.bufferData(
126 |             gl.ARRAY_BUFFER,
127 |             /*new Float32Array([
128 |                 1.0, 1.0,
129 |                  -1.0, 1.0,
130 |                 1.0,  -1.0,
131 |                 1.0,  -1.0,
132 |                 -1.0, 1.0,
133 |                 -1.0, -1.0]),*/
134 |             new Float32Array([
135 |                 1.0, 1.0,
136 |                 0.0, 1.0,
137 |                 1.0, 0.0,
138 |                 1.0, 0.0,
139 |                 0.0, 1.0,
140 |                 0.0, 0.0]),
141 |             gl.STATIC_DRAW);
142 |         gl.enableVertexAttribArray(texCoordLocation);
143 |         gl.vertexAttribPointer(texCoordLocation, 2, gl.FLOAT, false, 0, 0);
144 | 
145 |     }
146 |     
147 |     /**
148 |     * Set the MediaSource playing.
149 |     */
150 |     play(){
151 |         //console.log("Playing", this.id);
152 |         if (this.playing === false){
153 |             for (let i = 0; i < this.mediaSourceListeners.length; i++) {
154 |                 if(typeof this.mediaSourceListeners[i].play === 'function')this.mediaSourceListeners[i].play(this);
155 |             }    
156 |         }
157 |         this.playing = true;
158 |     }
159 |     /**
160 |     * Pause the MediaSource if it is playing.
161 |     */
162 |     pause(){
163 |         console.debug("Pausing", this.id);
164 |         this.playing = false;
165 |         for (let i = 0; i < this.mediaSourceListeners.length; i++) {
166 |             if(typeof this.mediaSourceListeners[i].pause === 'function')this.mediaSourceListeners[i].pause(this);
167 |         }
168 |     }
169 |     /**
170 |     * Seek the MediaSource to an appropriate point for the passed time.
171 |     * @param {number} seekTime - The time to seek too, this is the overall time for the whole playlist.
172 |     */
173 |     seek(seekTime){
174 |         //this.currentTime = seekTime;
175 |         for (let i = 0; i < this.mediaSourceListeners.length; i++) {
176 |             if(typeof this.mediaSourceListeners[i].seek === 'function')this.mediaSourceListeners[i].seek(this, seekTime);
177 |         }
178 |     }
179 |     /**
180 |     * Check if the MediaSource is ready to start playing.
181 |     */
182 |     isReady(){
183 |         let listenerReady = true;
184 |         for (let i = 0; i < this.mediaSourceListeners.length; i++) {
185 |             if(typeof this.mediaSourceListeners[i].isReady === 'function'){
186 |                 if (this.mediaSourceListeners[i].isReady(this) === false){
187 |                     listenerReady = false;
188 |                 }
189 |             }
190 |         }
191 |         if (listenerReady === true && this.ready === true) return true;
192 |         return false;
193 |     }
194 |     /**
195 |     * Set the MediaSource loading, when it's ready isReady() will return true.
196 |     */
197 |     load(){
198 |         console.debug("Loading", this.id);
199 |         for (let i = 0; i < this.mediaSourceListeners.length; i++) {
200 |             if(typeof this.mediaSourceListeners[i].load === 'function')this.mediaSourceListeners[i].load(this);
201 |         }
202 |         if (this.element !== undefined) {
203 |             return true;
204 |         }
205 |         return false;
206 |     }
207 |     /**
208 |     * Clean up the MediaSource for detruction.
209 |     */
210 |     destroy(){
211 |         console.debug("Destroying", this.id);
212 |         for (let i = 0; i < this.mediaSourceListeners.length; i++) {
213 |             if (typeof this.mediaSourceListeners[i].destroy === 'function') this.mediaSourceListeners[i].destroy(this);
214 |         }
215 |         if (this.disposeOfElementOnDestroy){
216 |             delete this.element;  
217 |         }
218 |     }
219 |     /**
220 |     * Render the MediaSource to the WebGL context passed into the constructor.
221 |     */
222 |     render(program, renderParameters, textures){
223 |         //renders the media source to the WebGL context using the pased program
224 |         let overriddenElement;
225 |         for (let i = 0; i < this.mediaSourceListeners.length; i++) {
226 |             if (typeof this.mediaSourceListeners[i].render === 'function'){
227 |                 let result =  this.mediaSourceListeners[i].render(this, renderParameters);
228 |                 if (result !== undefined) overriddenElement = result;
229 |             }
230 |         }
231 | 
232 |         this.gl.useProgram(program);
233 |         let renderParametersKeys = Object.keys(renderParameters);
234 |         let textureOffset = 1;
235 |         for (let index in renderParametersKeys){
236 |             let key = renderParametersKeys[index];
237 |             let parameterLoctation = this.gl.getUniformLocation(program, key);
238 |             if (parameterLoctation !== -1){
239 |                 if (typeof renderParameters[key] === "number"){
240 |                     this.gl.uniform1f(parameterLoctation, renderParameters[key]);
241 |                 }
242 |                 else if( Object.prototype.toString.call(renderParameters[key]) === '[object Array]'){
243 |                     let array = renderParameters[key];
244 |                     if(array.length === 1){
245 |                         this.gl.uniform1fv(parameterLoctation, array);
246 |                     } else if(array.length === 2){
247 |                         this.gl.uniform2fv(parameterLoctation, array);
248 |                     } else if(array.length === 3){
249 |                         this.gl.uniform3fv(parameterLoctation, array);
250 |                     } else if(array.length === 4){
251 |                         this.gl.uniform4fv(parameterLoctation, array);
252 |                     } else{
253 |                         console.debug("Shader parameter", key, "is too long and array:", array);
254 |                     }
255 |                 }
256 |                 else{
257 |                     //Is a texture
258 |                     this.gl.activeTexture(this.gl.TEXTURE0 + textureOffset);
259 |                     this.gl.uniform1i(parameterLoctation, textureOffset);
260 |                     this.gl.bindTexture(this.gl.TEXTURE_2D, textures[textureOffset-1]);
261 |                 }
262 |             }
263 |         }
264 |         
265 |         this.gl.activeTexture(this.gl.TEXTURE0);
266 |         let textureLocation = this.gl.getUniformLocation(program, "u_image");
267 |         this.gl.uniform1i(textureLocation, 0);
268 |         this.gl.bindTexture(this.gl.TEXTURE_2D, this.texture);
269 |         if (overriddenElement !== undefined){
270 |             this.gl.texImage2D(this.gl.TEXTURE_2D, 0, this.gl.RGBA, this.gl.RGBA, this.gl.UNSIGNED_BYTE, overriddenElement);
271 |         } else {
272 |             this.gl.texImage2D(this.gl.TEXTURE_2D, 0, this.gl.RGBA, this.gl.RGBA, this.gl.UNSIGNED_BYTE, this.element);
273 |         }
274 |         this.gl.drawArrays(this.gl.TRIANGLES, 0, 6);
275 |     }
276 |     onready(mediaSource){
277 |     }
278 | }
279 | 
280 | export default MediaSource;
281 | 282 |
283 | 284 | 287 | 288 | 289 | 290 | 291 | 292 | 293 | 294 | 295 | 296 | 297 | -------------------------------------------------------------------------------- /doc/file/src/sources/videosource.js.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | src/sources/videosource.js | HTML5 Video Compositor API Document 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 |
16 | Home 17 | Identifier 18 | Source 19 | 20 | 21 | 28 |
29 | 30 | 52 | 53 |

src/sources/videosource.js

54 |
//Matthew Shotton, R&D User Experince,© BBC 2015
 55 | import MediaSource from "./mediasource";
 56 | 
 57 | 
 58 | function eventOneTime(element, type, callback){
 59 |     let handleEvent = function(e){
 60 |         e.target.removeEventListener(e.type, handleEvent);
 61 |         return callback(e);
 62 |     };
 63 | 
 64 |     element.addEventListener(type, handleEvent, false);         
 65 | }
 66 | 
 67 | 
 68 | 
 69 | class VideoSource extends MediaSource{
 70 |     /**
 71 |     * Video playback source. Inherits from MediaSource 
 72 |     *
 73 |     * A VideoSource is the manifestation of a mediaSourceReference from a playlist object which has type "video". 
 74 |     * 
 75 |     * A VideoSource exists for a period slightly before a VideoSource is to play in order to give it time to preload and
 76 |     * is destroyed as soon as the VideoSource has finished playing. You can define an offset into the original video to 
 77 |     * start playing by passing in a sourceStart value in the properties.
 78 |     *
 79 |     * @param {Object} properties - An object with the following attributes: id, duration, start, sourceStart, and src or element. 
 80 |     * Where src is the URL of a video, or element is a DOM Video element.
 81 |     * 
 82 |     * @param {WebGLContext} gl - a webGl context to render too.
 83 |     */
 84 |     constructor(properties, gl){
 85 |         super(properties, gl);
 86 |         this.sourceStart = 0;
 87 |         this._volume = 1.0;
 88 |         if (properties.sourceStart !== undefined){
 89 |             this.sourceStart = properties.sourceStart;
 90 |         }
 91 |         if (properties.volume !== undefined){
 92 |             this._volume = properties.volume;
 93 |         }
 94 |     }
 95 |     /**
 96 |     * Set the VideoSource playing.
 97 |     */
 98 |     play(){
 99 |         super.play();
100 |         let _this = this;
101 | 
102 |         let playVideo = function(){
103 |             if (_this.element.readyState > 3){
104 |                 _this.ready = true;
105 |                 _this.element.play();
106 |             } else {
107 |                 console.debug("Can't play video due to readyState");
108 |                 _this.ready = false;
109 |                 eventOneTime(_this.element, "canplay", playVideo);
110 |             }
111 |         };
112 | 
113 |         playVideo();
114 |     }
115 |     /**
116 |     * Seek the VideoSource to an appropriate point for the passed time.
117 |     * @param {number} seekTime - The time to seek too, this is the overall time for the whole playlist.
118 |     */
119 |     seek(time){
120 |         super.seek();
121 |         let _this = this;
122 | 
123 |         let seekVideo = function(){
124 |             if (_this.element.readyState > 3){
125 |                 _this.ready = true;
126 |                 if ((time - _this.start) < 0 || time >(_this.start+_this.duration)){
127 |                     _this.element.currentTime = _this.sourceStart;
128 |                 } else {
129 |                     _this.element.currentTime = (time - _this.start) + _this.sourceStart;
130 |                 }
131 |             } else {
132 |                 //If the element isn't ready to seek create a one-time event which seeks the element once it is ready.
133 |                 console.debug("Can't seek video due to readyState");
134 |                 _this.ready = false;
135 |                 eventOneTime(_this.element, "canplay", seekVideo);
136 |             }
137 |         };
138 | 
139 |         seekVideo();  
140 |     }
141 |     /**
142 |     * Pause the VideoSource if it is playing.
143 |     */
144 |     pause(){
145 |         super.pause();
146 |         this.element.pause();
147 |     }
148 |     /**
149 |     * Set the VideoSource loading, when it's ready isReady() will return true.
150 |     */
151 |     load(){
152 |         //check if we're using an already instatiated element, if so don't do anything.
153 | 
154 |         if (super.load()){
155 |             //this.element.currentTime = this.sourceStart;
156 |             this.seek(0);
157 |             this.gl.texImage2D(this.gl.TEXTURE_2D, 0, this.gl.RGBA, this.gl.RGBA, this.gl.UNSIGNED_BYTE, this.element);
158 |             this.ready = true;
159 |             this.width = this.element.videoWidth;
160 |             this.height = this.element.videoHeight;
161 |             this.onready(this);
162 |             return;
163 |         }
164 |         //otherwise begin the loading process for this mediaSource
165 |         this.element = document.createElement('video');            
166 |         //construct a fragement URL to cut the required segment from the source video
167 |         this.element.src = this.src;
168 |         this.element.volume = this._volume;
169 |         this.element.preload = "auto";
170 |         this.element.load();
171 |         let _this = this;
172 |         this.element.addEventListener('loadeddata', function() {
173 |             _this.element.currentTime = _this.sourceStart;
174 |             _this.seek(0);
175 |             _this.gl.texImage2D(_this.gl.TEXTURE_2D, 0, _this.gl.RGBA, _this.gl.RGBA, _this.gl.UNSIGNED_BYTE, _this.element);
176 |             _this.ready = true;
177 |             _this.width = _this.element.videoWidth;
178 |             _this.height = _this.element.videoHeight;
179 |             _this.onready(_this);
180 |         }, false);
181 |         /*this.element.addEventListener('seeked', function(){
182 |             console.log("SEEKED");
183 |             _this.ready = true;
184 |             _this.onready(_this);
185 |         })*/
186 | 
187 | 
188 |     }
189 |     /**
190 |     * Render the VideoSource to the WebGL context passed into the constructor.
191 |     */
192 |     render(program, renderParameters, textures){
193 |         this.element.playbackRate = renderParameters["playback_rate"];
194 |         super.render(program, renderParameters, textures);
195 |     }
196 |     /**
197 |     * Clean up the VideoSource for detruction.
198 |     */
199 |     destroy(){
200 |         this.element.pause();
201 |         if (this.disposeOfElementOnDestroy){
202 |             this.element.src = "";
203 |             this.element.removeAttribute("src");    
204 |         }
205 |         super.destroy();
206 |     }
207 | }
208 | 
209 | export default VideoSource;
210 | 211 |
212 | 213 | 216 | 217 | 218 | 219 | 220 | 221 | 222 | 223 | 224 | 225 | 226 | -------------------------------------------------------------------------------- /doc/identifiers.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Index | HTML5 Video Compositor API Document 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 |
16 | Home 17 | Identifier 18 | Source 19 | 20 | 21 | 28 |
29 | 30 | 52 | 53 |

Identifier

54 |

Class Summary

55 | 56 | 57 | 58 | 59 | 66 | 78 | 82 | 83 | 84 | 91 | 103 | 107 | 108 | 109 | 116 | 128 | 132 | 133 | 134 | 141 | 153 | 157 | 158 | 159 | 166 | 178 | 182 | 183 | 184 | 191 | 203 | 207 | 208 | 209 | 216 | 228 | 232 | 233 | 234 | 241 | 253 | 257 | 258 | 259 |
Static Public Class Summary
60 | public 61 | 62 | 63 | 64 | 65 | 67 |
68 |

69 | AudioManager 70 |

71 |
72 |
73 | 74 | 75 | 76 |
77 |
79 | 80 | 81 |
85 | public 86 | 87 | 88 | 89 | 90 | 92 |
93 |

94 | CanvasSource 95 |

96 |
97 |
98 | 99 | 100 | 101 |
102 |
104 | 105 | 106 |
110 | public 111 | 112 | 113 | 114 | 115 | 117 |
118 |

119 | Effect 120 |

121 |
122 |
123 | 124 | 125 | 126 |
127 |
129 | 130 | 131 |
135 | public 136 | 137 | 138 | 139 | 140 | 142 |
143 |

144 | EffectManager 145 |

146 |
147 |
148 | 149 | 150 | 151 |
152 |
154 | 155 | 156 |
160 | public 161 | 162 | 163 | 164 | 165 | 167 |
168 |

169 | ImageSource 170 |

171 |
172 |
173 | 174 | 175 | 176 |
177 |
179 | 180 | 181 |
185 | public 186 | 187 | 188 | 189 | 190 | 192 |
193 |

194 | MediaSource 195 |

196 |
197 |
198 | 199 | 200 | 201 |
202 |
204 | 205 | 206 |
210 | public 211 | 212 | 213 | 214 | 215 | 217 |
218 |

219 | VideoCompositor 220 |

221 |
222 |
223 | 224 | 225 | 226 |
227 |
229 | 230 | 231 |
235 | public 236 | 237 | 238 | 239 | 240 | 242 |
243 |

244 | VideoSource 245 |

246 |
247 |
248 | 249 | 250 | 251 |
252 |
254 | 255 | 256 |
260 |
261 | 262 | 263 |

Variable Summary

264 | 265 | 266 | 267 | 268 | 275 | 287 | 291 | 292 | 293 | 300 | 312 | 316 | 317 | 318 | 325 | 337 | 341 | 342 | 343 | 350 | 362 | 366 | 367 | 368 | 375 | 387 | 391 | 392 | 393 | 400 | 412 | 416 | 417 | 418 | 425 | 437 | 441 | 442 | 443 | 450 | 462 | 466 | 467 | 468 |
Static Public Variable Summary
269 | public 270 | 271 | 272 | 273 | 274 | 276 |
277 |

278 | audioManager: AudioManager 279 |

280 |
281 |
282 | 283 | 284 | 285 |
286 |
288 | 289 | 290 |
294 | public 295 | 296 | 297 | 298 | 299 | 301 |
302 |

303 | canvasSource: CanvasSource 304 |

305 |
306 |
307 | 308 | 309 | 310 |
311 |
313 | 314 | 315 |
319 | public 320 | 321 | 322 | 323 | 324 | 326 |
327 |

328 | effect: Effect 329 |

330 |
331 |
332 | 333 | 334 | 335 |
336 |
338 | 339 | 340 |
344 | public 345 | 346 | 347 | 348 | 349 | 351 |
352 |

353 | effectManager: EffectManager 354 |

355 |
356 |
357 | 358 | 359 | 360 |
361 |
363 | 364 | 365 |
369 | public 370 | 371 | 372 | 373 | 374 | 376 |
377 |

378 | imageSource: ImageSource 379 |

380 |
381 |
382 | 383 | 384 | 385 |
386 |
388 | 389 | 390 |
394 | public 395 | 396 | 397 | 398 | 399 | 401 |
402 |

403 | mediaSource: MediaSource 404 |

405 |
406 |
407 | 408 | 409 | 410 |
411 |
413 | 414 | 415 |
419 | public 420 | 421 | 422 | 423 | 424 | 426 |
427 |

428 | videoCompositor: VideoCompositor 429 |

430 |
431 |
432 | 433 | 434 | 435 |
436 |
438 | 439 | 440 |
444 | public 445 | 446 | 447 | 448 | 449 | 451 |
452 |

453 | videoSource: VideoSource 454 |

455 |
456 |
457 | 458 | 459 | 460 |
461 |
463 | 464 | 465 |
469 |
470 | 471 |
472 | 473 | 476 | 477 | 478 | 479 | 480 | 481 | 482 | 483 | 484 | 485 | 486 | -------------------------------------------------------------------------------- /doc/image/badge.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | document 13 | document 14 | @ratio@ 15 | @ratio@ 16 | 17 | 18 | -------------------------------------------------------------------------------- /doc/image/github.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bbc/html5-video-compositor/6dcffdf315e3a6aaa4fff5526c164676a307742b/doc/image/github.png -------------------------------------------------------------------------------- /doc/image/search.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bbc/html5-video-compositor/6dcffdf315e3a6aaa4fff5526c164676a307742b/doc/image/search.png -------------------------------------------------------------------------------- /doc/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | HTML5 Video Compositor API Document 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 |
16 | Home 17 | Identifier 18 | Source 19 | 20 | 21 | 28 |
29 | 30 | 52 | 53 |

HTML5-Video-Compositor

54 |

A shader based video composition engine for the browser.

55 |
<!DOCTYPE html>
 56 | <html>
 57 | <head></head>
 58 | <body>
 59 |     <script type="text/javascript" src="videocompositor.js"></script>
 60 |     <script type="text/javascript">
 61 |         window.onload = function () {
 62 |             var canvas = document.getElementById('player-canvas');
 63 |             var videoCompositor = new VideoCompositor(canvas);
 64 | 
 65 |             videoCompositor.playlist = {
 66 |                 "tracks":[
 67 |                     [{type:"video", sourceStart:0, start:0, duration:5, src:"video1.mp4", id:"1"},                      {type:"video", sourceStart:0, start:7.5, duration:5, src:"video2.mp4", id:"3"}],
 68 |                     [                                      {type:"image", start:2.5, duration:5, src:"image.png", id:"2"}]
 69 |                 ]
 70 |             };
 71 | 
 72 |             videoCompositor.play();
 73 |         };
 74 |     </script>
 75 |     <canvas id="player-canvas"></canvas>
 76 | </body>
 77 | </html>
 78 | 
79 |

Introduction

80 |

This is an experimental video composition engine which can play edit decision lists in the browser. Content can be dynamically appended to the EDL as it's playing to create interactive and responsive content.

81 |

In video editing terms an EDL defines the points at which to cut and assemble video sources. VideoCompositor uses a simple JSON based EDL to describe how to cut and assemble HTML5 video sources, images, and WebGL contexts, it also provides a framework for performing shader based compositing operations (i.e cross-fades, green-screen).

82 |

Limitations

83 | 89 |

Video Encoding

90 |

You will probably only see acceptable video performance if you encode videos with some kind of "fast decode" option. Using the avconv tool this can be done with the following command.

91 |
avconv -i input.mp4 -tune fastdecode -strict experimental output.mp4
 92 | 
93 |

Documentation

94 |

The tutorial.md file in the tutorial directory of the project gives a walk-through of using the library to sequence clips and perform simple effects.

95 |

API documentation is available in the /doc directory of the project.

96 |

Notes about usage, behaviors, and other miscellaneous information is available on the Wiki.

97 |

Build

98 |

This project uses npm to manage dependencies. To build the compositor, in the root of the project run:

99 |
npm install
100 | 
101 |

Once this has completed you can build the source files by running (this will build a commonjs2 and a vanilla js file):

102 |
npm run build
103 | 
104 |

To auto-recompile the project on source change run (this will only rebuild the vanilla js file):

105 |
npm run dev
106 | 
107 |
108 |
109 | 110 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | -------------------------------------------------------------------------------- /doc/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "html5-video-compositor", 3 | "version": "0.0.1", 4 | "description": "A HTML5 based video composition engine", 5 | "contributors": [ 6 | { 7 | "name": "Matthew Shotton", 8 | "email": "matthew.shotton@bbc.co.uk" 9 | }, 10 | { 11 | "name": "Max Leonard", 12 | "email": "max.leonard@bbc.co.uk" 13 | } 14 | ], 15 | "scripts":{ 16 | "build_all":"node ./node_modules/webpack/bin/webpack.js --optimize-minimize --config webpack.config.js && node ./node_modules/webpack/bin/webpack.js --config webpack.commonjs2.config.js && node ./node_modules/esdoc/out/src/ESDocCLI.js -c esdoc.json", 17 | "doc":"node ./node_modules/esdoc/out/src/ESDocCLI.js -c esdoc.json", 18 | "build":"node ./node_modules/webpack/bin/webpack.js --optimize-minimize --config webpack.config.js && node ./node_modules/webpack/bin/webpack.js --config webpack.commonjs2.config.js", 19 | "dev":"node ./node_modules/webpack/bin/webpack.js --watch --config webpack.config.js" 20 | }, 21 | "dependencies": {}, 22 | "devDependencies": { 23 | "babel-core": "^5.6.5", 24 | "babel-loader": "^5.1.4", 25 | "node-libs-browser": "^0.5.2", 26 | "webpack": "^1.9.11", 27 | "esdoc": "^0.3.1" 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /doc/script/inherited-summary.js: -------------------------------------------------------------------------------- 1 | (function(){ 2 | function toggle(ev) { 3 | var button = ev.target; 4 | var parent = ev.target.parentElement; 5 | while(parent) { 6 | if (parent.tagName === 'TABLE' && parent.classList.contains('summary')) break; 7 | parent = parent.parentElement; 8 | } 9 | 10 | if (!parent) return; 11 | 12 | var tbody = parent.querySelector('tbody'); 13 | if (button.classList.contains('opened')) { 14 | button.classList.remove('opened'); 15 | button.classList.add('closed'); 16 | tbody.style.display = 'none'; 17 | } else { 18 | button.classList.remove('closed'); 19 | button.classList.add('opened'); 20 | tbody.style.display = 'block'; 21 | } 22 | } 23 | 24 | var buttons = document.querySelectorAll('.inherited-summary thead .toggle'); 25 | for (var i = 0; i < buttons.length; i++) { 26 | buttons[i].addEventListener('click', toggle); 27 | } 28 | })(); 29 | -------------------------------------------------------------------------------- /doc/script/inner-link.js: -------------------------------------------------------------------------------- 1 | // inner link(#foo) can not correctly scroll, because page has fixed header, 2 | // so, I manually scroll. 3 | (function(){ 4 | var matched = location.hash.match(/errorLines=([\d,]+)/); 5 | if (matched) return; 6 | 7 | function adjust() { 8 | window.scrollBy(0, -55); 9 | var el = document.querySelector('.inner-link-active'); 10 | if (el) el.classList.remove('inner-link-active'); 11 | 12 | var el = document.querySelector(location.hash); 13 | if (el) el.classList.add('inner-link-active'); 14 | } 15 | 16 | window.addEventListener('hashchange', adjust); 17 | 18 | if (location.hash) { 19 | setTimeout(adjust, 0); 20 | } 21 | })(); 22 | 23 | (function(){ 24 | var els = document.querySelectorAll('[href^="#"]'); 25 | for (var i = 0; i < els.length; i++) { 26 | var el = els[i]; 27 | el.href = location.href + el.getAttribute('href'); // because el.href is absolute path 28 | } 29 | })(); 30 | -------------------------------------------------------------------------------- /doc/script/patch-for-local.js: -------------------------------------------------------------------------------- 1 | (function(){ 2 | if (location.protocol === 'file:') { 3 | var elms = document.querySelectorAll('a[href="./"]'); 4 | for (var i = 0; i < elms.length; i++) { 5 | elms[i].href = './index.html'; 6 | } 7 | } 8 | })(); 9 | -------------------------------------------------------------------------------- /doc/script/prettify/Apache-License-2.0.txt: -------------------------------------------------------------------------------- 1 | 2 | Apache License 3 | Version 2.0, January 2004 4 | http://www.apache.org/licenses/ 5 | 6 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 7 | 8 | 1. Definitions. 9 | 10 | "License" shall mean the terms and conditions for use, reproduction, 11 | and distribution as defined by Sections 1 through 9 of this document. 12 | 13 | "Licensor" shall mean the copyright owner or entity authorized by 14 | the copyright owner that is granting the License. 15 | 16 | "Legal Entity" shall mean the union of the acting entity and all 17 | other entities that control, are controlled by, or are under common 18 | control with that entity. For the purposes of this definition, 19 | "control" means (i) the power, direct or indirect, to cause the 20 | direction or management of such entity, whether by contract or 21 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 22 | outstanding shares, or (iii) beneficial ownership of such entity. 23 | 24 | "You" (or "Your") shall mean an individual or Legal Entity 25 | exercising permissions granted by this License. 26 | 27 | "Source" form shall mean the preferred form for making modifications, 28 | including but not limited to software source code, documentation 29 | source, and configuration files. 30 | 31 | "Object" form shall mean any form resulting from mechanical 32 | transformation or translation of a Source form, including but 33 | not limited to compiled object code, generated documentation, 34 | and conversions to other media types. 35 | 36 | "Work" shall mean the work of authorship, whether in Source or 37 | Object form, made available under the License, as indicated by a 38 | copyright notice that is included in or attached to the work 39 | (an example is provided in the Appendix below). 40 | 41 | "Derivative Works" shall mean any work, whether in Source or Object 42 | form, that is based on (or derived from) the Work and for which the 43 | editorial revisions, annotations, elaborations, or other modifications 44 | represent, as a whole, an original work of authorship. For the purposes 45 | of this License, Derivative Works shall not include works that remain 46 | separable from, or merely link (or bind by name) to the interfaces of, 47 | the Work and Derivative Works thereof. 48 | 49 | "Contribution" shall mean any work of authorship, including 50 | the original version of the Work and any modifications or additions 51 | to that Work or Derivative Works thereof, that is intentionally 52 | submitted to Licensor for inclusion in the Work by the copyright owner 53 | or by an individual or Legal Entity authorized to submit on behalf of 54 | the copyright owner. For the purposes of this definition, "submitted" 55 | means any form of electronic, verbal, or written communication sent 56 | to the Licensor or its representatives, including but not limited to 57 | communication on electronic mailing lists, source code control systems, 58 | and issue tracking systems that are managed by, or on behalf of, the 59 | Licensor for the purpose of discussing and improving the Work, but 60 | excluding communication that is conspicuously marked or otherwise 61 | designated in writing by the copyright owner as "Not a Contribution." 62 | 63 | "Contributor" shall mean Licensor and any individual or Legal Entity 64 | on behalf of whom a Contribution has been received by Licensor and 65 | subsequently incorporated within the Work. 66 | 67 | 2. Grant of Copyright License. Subject to the terms and conditions of 68 | this License, each Contributor hereby grants to You a perpetual, 69 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 70 | copyright license to reproduce, prepare Derivative Works of, 71 | publicly display, publicly perform, sublicense, and distribute the 72 | Work and such Derivative Works in Source or Object form. 73 | 74 | 3. Grant of Patent License. Subject to the terms and conditions of 75 | this License, each Contributor hereby grants to You a perpetual, 76 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 77 | (except as stated in this section) patent license to make, have made, 78 | use, offer to sell, sell, import, and otherwise transfer the Work, 79 | where such license applies only to those patent claims licensable 80 | by such Contributor that are necessarily infringed by their 81 | Contribution(s) alone or by combination of their Contribution(s) 82 | with the Work to which such Contribution(s) was submitted. If You 83 | institute patent litigation against any entity (including a 84 | cross-claim or counterclaim in a lawsuit) alleging that the Work 85 | or a Contribution incorporated within the Work constitutes direct 86 | or contributory patent infringement, then any patent licenses 87 | granted to You under this License for that Work shall terminate 88 | as of the date such litigation is filed. 89 | 90 | 4. Redistribution. You may reproduce and distribute copies of the 91 | Work or Derivative Works thereof in any medium, with or without 92 | modifications, and in Source or Object form, provided that You 93 | meet the following conditions: 94 | 95 | (a) You must give any other recipients of the Work or 96 | Derivative Works a copy of this License; and 97 | 98 | (b) You must cause any modified files to carry prominent notices 99 | stating that You changed the files; and 100 | 101 | (c) You must retain, in the Source form of any Derivative Works 102 | that You distribute, all copyright, patent, trademark, and 103 | attribution notices from the Source form of the Work, 104 | excluding those notices that do not pertain to any part of 105 | the Derivative Works; and 106 | 107 | (d) If the Work includes a "NOTICE" text file as part of its 108 | distribution, then any Derivative Works that You distribute must 109 | include a readable copy of the attribution notices contained 110 | within such NOTICE file, excluding those notices that do not 111 | pertain to any part of the Derivative Works, in at least one 112 | of the following places: within a NOTICE text file distributed 113 | as part of the Derivative Works; within the Source form or 114 | documentation, if provided along with the Derivative Works; or, 115 | within a display generated by the Derivative Works, if and 116 | wherever such third-party notices normally appear. The contents 117 | of the NOTICE file are for informational purposes only and 118 | do not modify the License. You may add Your own attribution 119 | notices within Derivative Works that You distribute, alongside 120 | or as an addendum to the NOTICE text from the Work, provided 121 | that such additional attribution notices cannot be construed 122 | as modifying the License. 123 | 124 | You may add Your own copyright statement to Your modifications and 125 | may provide additional or different license terms and conditions 126 | for use, reproduction, or distribution of Your modifications, or 127 | for any such Derivative Works as a whole, provided Your use, 128 | reproduction, and distribution of the Work otherwise complies with 129 | the conditions stated in this License. 130 | 131 | 5. Submission of Contributions. Unless You explicitly state otherwise, 132 | any Contribution intentionally submitted for inclusion in the Work 133 | by You to the Licensor shall be under the terms and conditions of 134 | this License, without any additional terms or conditions. 135 | Notwithstanding the above, nothing herein shall supersede or modify 136 | the terms of any separate license agreement you may have executed 137 | with Licensor regarding such Contributions. 138 | 139 | 6. Trademarks. This License does not grant permission to use the trade 140 | names, trademarks, service marks, or product names of the Licensor, 141 | except as required for reasonable and customary use in describing the 142 | origin of the Work and reproducing the content of the NOTICE file. 143 | 144 | 7. Disclaimer of Warranty. Unless required by applicable law or 145 | agreed to in writing, Licensor provides the Work (and each 146 | Contributor provides its Contributions) on an "AS IS" BASIS, 147 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 148 | implied, including, without limitation, any warranties or conditions 149 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 150 | PARTICULAR PURPOSE. You are solely responsible for determining the 151 | appropriateness of using or redistributing the Work and assume any 152 | risks associated with Your exercise of permissions under this License. 153 | 154 | 8. Limitation of Liability. In no event and under no legal theory, 155 | whether in tort (including negligence), contract, or otherwise, 156 | unless required by applicable law (such as deliberate and grossly 157 | negligent acts) or agreed to in writing, shall any Contributor be 158 | liable to You for damages, including any direct, indirect, special, 159 | incidental, or consequential damages of any character arising as a 160 | result of this License or out of the use or inability to use the 161 | Work (including but not limited to damages for loss of goodwill, 162 | work stoppage, computer failure or malfunction, or any and all 163 | other commercial damages or losses), even if such Contributor 164 | has been advised of the possibility of such damages. 165 | 166 | 9. Accepting Warranty or Additional Liability. While redistributing 167 | the Work or Derivative Works thereof, You may choose to offer, 168 | and charge a fee for, acceptance of support, warranty, indemnity, 169 | or other liability obligations and/or rights consistent with this 170 | License. However, in accepting such obligations, You may act only 171 | on Your own behalf and on Your sole responsibility, not on behalf 172 | of any other Contributor, and only if You agree to indemnify, 173 | defend, and hold each Contributor harmless for any liability 174 | incurred by, or claims asserted against, such Contributor by reason 175 | of your accepting any such warranty or additional liability. 176 | 177 | END OF TERMS AND CONDITIONS 178 | 179 | APPENDIX: How to apply the Apache License to your work. 180 | 181 | To apply the Apache License to your work, attach the following 182 | boilerplate notice, with the fields enclosed by brackets "[]" 183 | replaced with your own identifying information. (Don't include 184 | the brackets!) The text should be enclosed in the appropriate 185 | comment syntax for the file format. We also recommend that a 186 | file or class name and description of purpose be included on the 187 | same "printed page" as the copyright notice for easier 188 | identification within third-party archives. 189 | 190 | Copyright [yyyy] [name of copyright owner] 191 | 192 | Licensed under the Apache License, Version 2.0 (the "License"); 193 | you may not use this file except in compliance with the License. 194 | You may obtain a copy of the License at 195 | 196 | http://www.apache.org/licenses/LICENSE-2.0 197 | 198 | Unless required by applicable law or agreed to in writing, software 199 | distributed under the License is distributed on an "AS IS" BASIS, 200 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 201 | See the License for the specific language governing permissions and 202 | limitations under the License. 203 | -------------------------------------------------------------------------------- /doc/script/prettify/prettify.js: -------------------------------------------------------------------------------- 1 | var q=null;window.PR_SHOULD_USE_CONTINUATION=!0; 2 | (function(){function L(a){function m(a){var f=a.charCodeAt(0);if(f!==92)return f;var b=a.charAt(1);return(f=r[b])?f:"0"<=b&&b<="7"?parseInt(a.substring(1),8):b==="u"||b==="x"?parseInt(a.substring(2),16):a.charCodeAt(1)}function e(a){if(a<32)return(a<16?"\\x0":"\\x")+a.toString(16);a=String.fromCharCode(a);if(a==="\\"||a==="-"||a==="["||a==="]")a="\\"+a;return a}function h(a){for(var f=a.substring(1,a.length-1).match(/\\u[\dA-Fa-f]{4}|\\x[\dA-Fa-f]{2}|\\[0-3][0-7]{0,2}|\\[0-7]{1,2}|\\[\S\s]|[^\\]/g),a= 3 | [],b=[],o=f[0]==="^",c=o?1:0,i=f.length;c122||(d<65||j>90||b.push([Math.max(65,j)|32,Math.min(d,90)|32]),d<97||j>122||b.push([Math.max(97,j)&-33,Math.min(d,122)&-33]))}}b.sort(function(a,f){return a[0]-f[0]||f[1]-a[1]});f=[];j=[NaN,NaN];for(c=0;ci[0]&&(i[1]+1>i[0]&&b.push("-"),b.push(e(i[1])));b.push("]");return b.join("")}function y(a){for(var f=a.source.match(/\[(?:[^\\\]]|\\[\S\s])*]|\\u[\dA-Fa-f]{4}|\\x[\dA-Fa-f]{2}|\\\d+|\\[^\dux]|\(\?[!:=]|[()^]|[^()[\\^]+/g),b=f.length,d=[],c=0,i=0;c=2&&a==="["?f[c]=h(j):a!=="\\"&&(f[c]=j.replace(/[A-Za-z]/g,function(a){a=a.charCodeAt(0);return"["+String.fromCharCode(a&-33,a|32)+"]"}));return f.join("")}for(var t=0,s=!1,l=!1,p=0,d=a.length;p=5&&"lang-"===b.substring(0,5))&&!(o&&typeof o[1]==="string"))c=!1,b="src";c||(r[f]=b)}i=d;d+=f.length;if(c){c=o[1];var j=f.indexOf(c),k=j+c.length;o[2]&&(k=f.length-o[2].length,j=k-c.length);b=b.substring(5);B(l+i,f.substring(0,j),e,p);B(l+i+j,c,C(b,c),p);B(l+i+k,f.substring(k),e,p)}else p.push(l+i,b)}a.e=p}var h={},y;(function(){for(var e=a.concat(m), 9 | l=[],p={},d=0,g=e.length;d=0;)h[n.charAt(k)]=r;r=r[1];n=""+r;p.hasOwnProperty(n)||(l.push(r),p[n]=q)}l.push(/[\S\s]/);y=L(l)})();var t=m.length;return e}function u(a){var m=[],e=[];a.tripleQuotedStrings?m.push(["str",/^(?:'''(?:[^'\\]|\\[\S\s]|''?(?=[^']))*(?:'''|$)|"""(?:[^"\\]|\\[\S\s]|""?(?=[^"]))*(?:"""|$)|'(?:[^'\\]|\\[\S\s])*(?:'|$)|"(?:[^"\\]|\\[\S\s])*(?:"|$))/,q,"'\""]):a.multiLineStrings?m.push(["str",/^(?:'(?:[^'\\]|\\[\S\s])*(?:'|$)|"(?:[^"\\]|\\[\S\s])*(?:"|$)|`(?:[^\\`]|\\[\S\s])*(?:`|$))/, 10 | q,"'\"`"]):m.push(["str",/^(?:'(?:[^\n\r'\\]|\\.)*(?:'|$)|"(?:[^\n\r"\\]|\\.)*(?:"|$))/,q,"\"'"]);a.verbatimStrings&&e.push(["str",/^@"(?:[^"]|"")*(?:"|$)/,q]);var h=a.hashComments;h&&(a.cStyleComments?(h>1?m.push(["com",/^#(?:##(?:[^#]|#(?!##))*(?:###|$)|.*)/,q,"#"]):m.push(["com",/^#(?:(?:define|elif|else|endif|error|ifdef|include|ifndef|line|pragma|undef|warning)\b|[^\n\r]*)/,q,"#"]),e.push(["str",/^<(?:(?:(?:\.\.\/)*|\/?)(?:[\w-]+(?:\/[\w-]+)+)?[\w-]+\.h|[a-z]\w*)>/,q])):m.push(["com",/^#[^\n\r]*/, 11 | q,"#"]));a.cStyleComments&&(e.push(["com",/^\/\/[^\n\r]*/,q]),e.push(["com",/^\/\*[\S\s]*?(?:\*\/|$)/,q]));a.regexLiterals&&e.push(["lang-regex",/^(?:^^\.?|[!+-]|!=|!==|#|%|%=|&|&&|&&=|&=|\(|\*|\*=|\+=|,|-=|->|\/|\/=|:|::|;|<|<<|<<=|<=|=|==|===|>|>=|>>|>>=|>>>|>>>=|[?@[^]|\^=|\^\^|\^\^=|{|\||\|=|\|\||\|\|=|~|break|case|continue|delete|do|else|finally|instanceof|return|throw|try|typeof)\s*(\/(?=[^*/])(?:[^/[\\]|\\[\S\s]|\[(?:[^\\\]]|\\[\S\s])*(?:]|$))+\/)/]);(h=a.types)&&e.push(["typ",h]);a=(""+a.keywords).replace(/^ | $/g, 12 | "");a.length&&e.push(["kwd",RegExp("^(?:"+a.replace(/[\s,]+/g,"|")+")\\b"),q]);m.push(["pln",/^\s+/,q," \r\n\t\xa0"]);e.push(["lit",/^@[$_a-z][\w$@]*/i,q],["typ",/^(?:[@_]?[A-Z]+[a-z][\w$@]*|\w+_t\b)/,q],["pln",/^[$_a-z][\w$@]*/i,q],["lit",/^(?:0x[\da-f]+|(?:\d(?:_\d+)*\d*(?:\.\d*)?|\.\d\+)(?:e[+-]?\d+)?)[a-z]*/i,q,"0123456789"],["pln",/^\\[\S\s]?/,q],["pun",/^.[^\s\w"-$'./@\\`]*/,q]);return x(m,e)}function D(a,m){function e(a){switch(a.nodeType){case 1:if(k.test(a.className))break;if("BR"===a.nodeName)h(a), 13 | a.parentNode&&a.parentNode.removeChild(a);else for(a=a.firstChild;a;a=a.nextSibling)e(a);break;case 3:case 4:if(p){var b=a.nodeValue,d=b.match(t);if(d){var c=b.substring(0,d.index);a.nodeValue=c;(b=b.substring(d.index+d[0].length))&&a.parentNode.insertBefore(s.createTextNode(b),a.nextSibling);h(a);c||a.parentNode.removeChild(a)}}}}function h(a){function b(a,d){var e=d?a.cloneNode(!1):a,f=a.parentNode;if(f){var f=b(f,1),g=a.nextSibling;f.appendChild(e);for(var h=g;h;h=g)g=h.nextSibling,f.appendChild(h)}return e} 14 | for(;!a.nextSibling;)if(a=a.parentNode,!a)return;for(var a=b(a.nextSibling,0),e;(e=a.parentNode)&&e.nodeType===1;)a=e;d.push(a)}var k=/(?:^|\s)nocode(?:\s|$)/,t=/\r\n?|\n/,s=a.ownerDocument,l;a.currentStyle?l=a.currentStyle.whiteSpace:window.getComputedStyle&&(l=s.defaultView.getComputedStyle(a,q).getPropertyValue("white-space"));var p=l&&"pre"===l.substring(0,3);for(l=s.createElement("LI");a.firstChild;)l.appendChild(a.firstChild);for(var d=[l],g=0;g=0;){var h=m[e];A.hasOwnProperty(h)?window.console&&console.warn("cannot override language handler %s",h):A[h]=a}}function C(a,m){if(!a||!A.hasOwnProperty(a))a=/^\s*=o&&(h+=2);e>=c&&(a+=2)}}catch(w){"console"in window&&console.log(w&&w.stack?w.stack:w)}}var v=["break,continue,do,else,for,if,return,while"],w=[[v,"auto,case,char,const,default,double,enum,extern,float,goto,int,long,register,short,signed,sizeof,static,struct,switch,typedef,union,unsigned,void,volatile"], 18 | "catch,class,delete,false,import,new,operator,private,protected,public,this,throw,true,try,typeof"],F=[w,"alignof,align_union,asm,axiom,bool,concept,concept_map,const_cast,constexpr,decltype,dynamic_cast,explicit,export,friend,inline,late_check,mutable,namespace,nullptr,reinterpret_cast,static_assert,static_cast,template,typeid,typename,using,virtual,where"],G=[w,"abstract,boolean,byte,extends,final,finally,implements,import,instanceof,null,native,package,strictfp,super,synchronized,throws,transient"], 19 | H=[G,"as,base,by,checked,decimal,delegate,descending,dynamic,event,fixed,foreach,from,group,implicit,in,interface,internal,into,is,lock,object,out,override,orderby,params,partial,readonly,ref,sbyte,sealed,stackalloc,string,select,uint,ulong,unchecked,unsafe,ushort,var"],w=[w,"debugger,eval,export,function,get,null,set,undefined,var,with,Infinity,NaN"],I=[v,"and,as,assert,class,def,del,elif,except,exec,finally,from,global,import,in,is,lambda,nonlocal,not,or,pass,print,raise,try,with,yield,False,True,None"], 20 | J=[v,"alias,and,begin,case,class,def,defined,elsif,end,ensure,false,in,module,next,nil,not,or,redo,rescue,retry,self,super,then,true,undef,unless,until,when,yield,BEGIN,END"],v=[v,"case,done,elif,esac,eval,fi,function,in,local,set,then,until"],K=/^(DIR|FILE|vector|(de|priority_)?queue|list|stack|(const_)?iterator|(multi)?(set|map)|bitset|u?(int|float)\d*)/,N=/\S/,O=u({keywords:[F,H,w,"caller,delete,die,do,dump,elsif,eval,exit,foreach,for,goto,if,import,last,local,my,next,no,our,print,package,redo,require,sub,undef,unless,until,use,wantarray,while,BEGIN,END"+ 21 | I,J,v],hashComments:!0,cStyleComments:!0,multiLineStrings:!0,regexLiterals:!0}),A={};k(O,["default-code"]);k(x([],[["pln",/^[^]*(?:>|$)/],["com",/^<\!--[\S\s]*?(?:--\>|$)/],["lang-",/^<\?([\S\s]+?)(?:\?>|$)/],["lang-",/^<%([\S\s]+?)(?:%>|$)/],["pun",/^(?:<[%?]|[%?]>)/],["lang-",/^]*>([\S\s]+?)<\/xmp\b[^>]*>/i],["lang-js",/^]*>([\S\s]*?)(<\/script\b[^>]*>)/i],["lang-css",/^]*>([\S\s]*?)(<\/style\b[^>]*>)/i],["lang-in.tag",/^(<\/?[a-z][^<>]*>)/i]]), 22 | ["default-markup","htm","html","mxml","xhtml","xml","xsl"]);k(x([["pln",/^\s+/,q," \t\r\n"],["atv",/^(?:"[^"]*"?|'[^']*'?)/,q,"\"'"]],[["tag",/^^<\/?[a-z](?:[\w-.:]*\w)?|\/?>$/i],["atn",/^(?!style[\s=]|on)[a-z](?:[\w:-]*\w)?/i],["lang-uq.val",/^=\s*([^\s"'>]*(?:[^\s"'/>]|\/(?=\s)))/],["pun",/^[/<->]+/],["lang-js",/^on\w+\s*=\s*"([^"]+)"/i],["lang-js",/^on\w+\s*=\s*'([^']+)'/i],["lang-js",/^on\w+\s*=\s*([^\s"'>]+)/i],["lang-css",/^style\s*=\s*"([^"]+)"/i],["lang-css",/^style\s*=\s*'([^']+)'/i],["lang-css", 23 | /^style\s*=\s*([^\s"'>]+)/i]]),["in.tag"]);k(x([],[["atv",/^[\S\s]+/]]),["uq.val"]);k(u({keywords:F,hashComments:!0,cStyleComments:!0,types:K}),["c","cc","cpp","cxx","cyc","m"]);k(u({keywords:"null,true,false"}),["json"]);k(u({keywords:H,hashComments:!0,cStyleComments:!0,verbatimStrings:!0,types:K}),["cs"]);k(u({keywords:G,cStyleComments:!0}),["java"]);k(u({keywords:v,hashComments:!0,multiLineStrings:!0}),["bsh","csh","sh"]);k(u({keywords:I,hashComments:!0,multiLineStrings:!0,tripleQuotedStrings:!0}), 24 | ["cv","py"]);k(u({keywords:"caller,delete,die,do,dump,elsif,eval,exit,foreach,for,goto,if,import,last,local,my,next,no,our,print,package,redo,require,sub,undef,unless,until,use,wantarray,while,BEGIN,END",hashComments:!0,multiLineStrings:!0,regexLiterals:!0}),["perl","pl","pm"]);k(u({keywords:J,hashComments:!0,multiLineStrings:!0,regexLiterals:!0}),["rb"]);k(u({keywords:w,cStyleComments:!0,regexLiterals:!0}),["js"]);k(u({keywords:"all,and,by,catch,class,else,extends,false,finally,for,if,in,is,isnt,loop,new,no,not,null,of,off,on,or,return,super,then,true,try,unless,until,when,while,yes", 25 | hashComments:3,cStyleComments:!0,multilineStrings:!0,tripleQuotedStrings:!0,regexLiterals:!0}),["coffee"]);k(x([],[["str",/^[\S\s]+/]]),["regex"]);window.prettyPrintOne=function(a,m,e){var h=document.createElement("PRE");h.innerHTML=a;e&&D(h,e);E({g:m,i:e,h:h});return h.innerHTML};window.prettyPrint=function(a){function m(){for(var e=window.PR_SHOULD_USE_CONTINUATION?l.now()+250:Infinity;p=0){var k=k.match(g),f,b;if(b= 26 | !k){b=n;for(var o=void 0,c=b.firstChild;c;c=c.nextSibling)var i=c.nodeType,o=i===1?o?b:c:i===3?N.test(c.nodeValue)?b:o:o;b=(f=o===b?void 0:o)&&"CODE"===f.tagName}b&&(k=f.className.match(g));k&&(k=k[1]);b=!1;for(o=n.parentNode;o;o=o.parentNode)if((o.tagName==="pre"||o.tagName==="code"||o.tagName==="xmp")&&o.className&&o.className.indexOf("prettyprint")>=0){b=!0;break}b||((b=(b=n.className.match(/\blinenums\b(?::(\d+))?/))?b[1]&&b[1].length?+b[1]:!0:!1)&&D(n,b),d={g:k,h:n,i:b},E(d))}}p' + pair[2] + ''); 35 | } 36 | } 37 | 38 | var innerHTML = ''; 39 | for (kind in html) { 40 | var list = html[kind]; 41 | if (!list.length) continue; 42 | innerHTML += '
  • ' + kind + '
  • \n' + list.join('\n'); 43 | } 44 | result.innerHTML = innerHTML; 45 | if (innerHTML) result.style.display = 'block'; 46 | selectedIndex = -1; 47 | }); 48 | 49 | // down, up and enter key are pressed, select search result. 50 | input.addEventListener('keydown', function(ev){ 51 | if (ev.keyCode === 40) { 52 | // arrow down 53 | var current = result.children[selectedIndex]; 54 | var selected = result.children[selectedIndex + 1]; 55 | if (selected && selected.classList.contains('search-separator')) { 56 | var selected = result.children[selectedIndex + 2]; 57 | selectedIndex++; 58 | } 59 | 60 | if (selected) { 61 | if (current) current.classList.remove('selected'); 62 | selectedIndex++; 63 | selected.classList.add('selected'); 64 | } 65 | } else if (ev.keyCode === 38) { 66 | // arrow up 67 | var current = result.children[selectedIndex]; 68 | var selected = result.children[selectedIndex - 1]; 69 | if (selected && selected.classList.contains('search-separator')) { 70 | var selected = result.children[selectedIndex - 2]; 71 | selectedIndex--; 72 | } 73 | 74 | if (selected) { 75 | if (current) current.classList.remove('selected'); 76 | selectedIndex--; 77 | selected.classList.add('selected'); 78 | } 79 | } else if (ev.keyCode === 13) { 80 | // enter 81 | var current = result.children[selectedIndex]; 82 | if (current) { 83 | var link = current.querySelector('a'); 84 | if (link) location.href = link.href; 85 | } 86 | } else { 87 | return; 88 | } 89 | 90 | ev.preventDefault(); 91 | }); 92 | 93 | // select search result when search result is mouse over. 94 | result.addEventListener('mousemove', function(ev){ 95 | var current = result.children[selectedIndex]; 96 | if (current) current.classList.remove('selected'); 97 | 98 | var li = ev.target; 99 | while (li) { 100 | if (li.nodeName === 'LI') break; 101 | li = li.parentElement; 102 | } 103 | 104 | if (li) { 105 | selectedIndex = Array.prototype.indexOf.call(result.children, li); 106 | li.classList.add('selected'); 107 | } 108 | }); 109 | 110 | // clear search result when body is clicked. 111 | document.body.addEventListener('click', function(ev){ 112 | selectedIndex = -1; 113 | result.style.display = 'none'; 114 | result.innerHTML = ''; 115 | }); 116 | 117 | })(); 118 | -------------------------------------------------------------------------------- /doc/script/test-summary.js: -------------------------------------------------------------------------------- 1 | (function(){ 2 | function toggle(ev) { 3 | var button = ev.target; 4 | var parent = ev.target.parentElement; 5 | while(parent) { 6 | if (parent.tagName === 'TR' && parent.classList.contains('test-describe')) break; 7 | parent = parent.parentElement; 8 | } 9 | 10 | if (!parent) return; 11 | 12 | var direction; 13 | if (button.classList.contains('opened')) { 14 | button.classList.remove('opened'); 15 | button.classList.add('closed'); 16 | direction = 'closed'; 17 | } else { 18 | button.classList.remove('closed'); 19 | button.classList.add('opened'); 20 | direction = 'opened'; 21 | } 22 | 23 | var targetDepth = parseInt(parent.dataset.testDepth, 10) + 1; 24 | var nextElement = parent.nextElementSibling; 25 | while (nextElement) { 26 | var depth = parseInt(nextElement.dataset.testDepth, 10); 27 | if (depth >= targetDepth) { 28 | if (direction === 'opened') { 29 | if (depth === targetDepth) nextElement.style.display = ''; 30 | } else if (direction === 'closed') { 31 | nextElement.style.display = 'none'; 32 | var innerButton = nextElement.querySelector('.toggle'); 33 | if (innerButton && innerButton.classList.contains('opened')) { 34 | innerButton.classList.remove('opened'); 35 | innerButton.classList.add('closed'); 36 | } 37 | } 38 | } else { 39 | break; 40 | } 41 | nextElement = nextElement.nextElementSibling; 42 | } 43 | } 44 | 45 | var buttons = document.querySelectorAll('.test-summary tr.test-describe .toggle'); 46 | for (var i = 0; i < buttons.length; i++) { 47 | buttons[i].addEventListener('click', toggle); 48 | } 49 | 50 | var topDescribes = document.querySelectorAll('.test-summary tr[data-test-depth="0"]'); 51 | for (var i = 0; i < topDescribes.length; i++) { 52 | topDescribes[i].style.display = ''; 53 | } 54 | })(); 55 | -------------------------------------------------------------------------------- /doc/source.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Source | HTML5 Video Compositor API Document 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 |
    16 | Home 17 | Identifier 18 | Source 19 | 20 | 21 | 28 |
    29 | 30 | 52 | 53 |

    Source 47/123

    54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 135 | 136 | 137 | 138 | 139 | 140 | 141 |
    FileIdentifierDocumentSizeLinesUpdated
    src/audiomanager.jsAudioManager 71 | audioManager0 %0/123412 byte1032015-11-05 14:00:30 (UTC)
    src/effect.jsEffect 80 | effect0 %0/115406 byte1402015-10-03 15:07:20 (UTC)
    src/effectmanager.jsEffectManager 89 | effectManager0 %0/81343 byte432015-09-02 12:32:08 (UTC)
    src/main.jsVideoCompositor 98 | videoCompositor73 %22/3064027 byte14572016-01-04 17:07:45 (UTC)
    src/sources/canvassource.jsCanvasSource 107 | canvasSource41 %5/122587 byte712015-10-05 11:25:29 (UTC)
    src/sources/imagesource.jsImageSource 116 | imageSource41 %5/122746 byte742015-10-05 11:25:29 (UTC)
    src/sources/mediasource.jsMediaSource 125 | mediaSource33 %8/249278 byte2262015-10-05 11:25:29 (UTC)
    src/sources/videosource.jsVideoSource 134 | videoSource50 %7/145458 byte1552016-01-02 18:05:39 (UTC)
    142 |
    143 | 144 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | 157 | -------------------------------------------------------------------------------- /esdoc.json: -------------------------------------------------------------------------------- 1 | { 2 | "source": "./src", 3 | "destination": "./doc", 4 | "title": "HTML5 Video Compositor" 5 | } -------------------------------------------------------------------------------- /examples/progress-bar.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Progress Bar Example 5 | 6 | 63 | 64 | 65 | 66 |

    67 | 68 |

    69 |

    70 | 71 | 72 |

    73 | 74 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "html5-video-compositor", 3 | "version": " 0.9.3", 4 | "description": "A HTML5 based video composition engine", 5 | "keywords": [ 6 | "html5", 7 | "video", 8 | "video composition", 9 | "webgl" 10 | ], 11 | "bugs": { 12 | "url": "https://github.com/bbc/html5-video-compositor/issues" 13 | }, 14 | "license": { 15 | "license": "Apache-2.0" 16 | }, 17 | "browser": "./src/main.js", 18 | "repository": { 19 | "type": "git", 20 | "url": "https://github.com/bbc/html5-video-compositor" 21 | }, 22 | "contributors": [ 23 | { 24 | "name": "Matthew Shotton", 25 | "email": "matthew.shotton@bbc.co.uk" 26 | }, 27 | { 28 | "name": "Max Leonard", 29 | "email": "max.leonard@bbc.co.uk" 30 | } 31 | ], 32 | "scripts": { 33 | "build_all": "node ./node_modules/webpack/bin/webpack.js --optimize-minimize --config webpack.config.js && node ./node_modules/webpack/bin/webpack.js --config webpack.commonjs2.config.js && node ./node_modules/esdoc/out/src/ESDocCLI.js -c esdoc.json", 34 | "doc": "node ./node_modules/esdoc/out/src/ESDocCLI.js -c esdoc.json", 35 | "build": "node ./node_modules/webpack/bin/webpack.js --optimize-minimize --config webpack.config.js && node ./node_modules/webpack/bin/webpack.js --config webpack.commonjs2.config.js", 36 | "dev": "node ./node_modules/webpack/bin/webpack.js --watch --config webpack.config.js & ./node_modules/http-server/bin/http-server" 37 | }, 38 | "dependencies": {}, 39 | "browserDependencies": {}, 40 | "devDependencies": { 41 | "babel-core": "^5.6.5", 42 | "babel-loader": "^5.1.4", 43 | "esdoc": "^0.3.1", 44 | "http-server": "^0.9.0", 45 | "node-libs-browser": "^0.5.2", 46 | "webpack": "^1.9.11" 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /src/audiomanager.js: -------------------------------------------------------------------------------- 1 | 2 | 3 | function isIdInTrack(id, track){ 4 | for (let i = 0; i < track.length; i++) { 5 | if (track[i].id === id){ 6 | return true; 7 | } 8 | } 9 | return false; 10 | } 11 | 12 | function getTrackIndexsForId(id, tracks){ 13 | let trackIndexs = []; 14 | for (let i = 0; i < tracks.length; i++) { 15 | let track = tracks[i]; 16 | if (isIdInTrack(id, track)){ 17 | trackIndexs.push(i); 18 | } 19 | } 20 | return trackIndexs; 21 | } 22 | 23 | 24 | class AudioManager { 25 | constructor(audioCtx){ 26 | this.audioCtx = audioCtx; 27 | this.tracks = []; 28 | this.audioNodes = new Map(); 29 | this.audioOutputNodes = []; 30 | } 31 | 32 | createAudioNodeFromTrack(track){ 33 | if (this.audioCtx === undefined){ 34 | // There can only be a max of 6 AudioContexts in most browsers, so only instantiate it here rather than in 35 | // constructor as it's genuinley needed. Otherwise having >6 VideoCompositor instances running will break 36 | // the browser. 37 | this.audioCtx = new AudioContext(); 38 | } 39 | this.tracks.push(track); 40 | let trackBus = this.audioCtx.createGain(); 41 | this.audioOutputNodes.push(trackBus); 42 | return trackBus; 43 | } 44 | 45 | getAudioContext(){ 46 | if (this.audioCtx === undefined){ 47 | // There can only be a max of 6 AudioContexts in most browsers, so only instantiate it here rather than in 48 | // constructor as it's genuinley needed. Otherwise having >6 VideoCompositor instances running will break 49 | // the browser. 50 | this.audioCtx = new AudioContext(); 51 | } 52 | return this.audioCtx; 53 | } 54 | 55 | removeFromCacheById(id){ 56 | let node = this.audioNodes.get(id); 57 | node.disconnect(); 58 | this.audioNodes.delete(id); 59 | } 60 | 61 | clearAudioNodeCache(){ 62 | for (let id of this.audioNodes.keys()){ 63 | this.removeFromCacheById(id); 64 | } 65 | } 66 | 67 | update(mediaSources, currentlyPlaying){ 68 | let currentlyPlayingIds = []; 69 | for (let i = 0; i < currentlyPlaying.length; i++) { 70 | let mediaSourceRef = currentlyPlaying[i]; 71 | currentlyPlayingIds.push(mediaSourceRef.id); 72 | } 73 | 74 | if (mediaSources === undefined) return; 75 | for (let id of mediaSources.keys()) { 76 | let mediaSource = mediaSources.get(id); 77 | let trackIndexs = getTrackIndexsForId(id, this.tracks); 78 | if (trackIndexs.length ===0){ 79 | continue; //No mappings for this id 80 | } 81 | if (!this.audioNodes.has(id)){ 82 | //if an AudioNode for this id does not exist, create it. 83 | let audioNode; 84 | try{ 85 | audioNode = this.audioCtx.createMediaElementSource(mediaSource.element); 86 | } catch (err) { 87 | continue; 88 | } 89 | 90 | this.audioNodes.set(id, audioNode); 91 | //make the connections from the audio node to the appropriate output tracks 92 | for (let i = 0; i < trackIndexs.length; i++) { 93 | let trackIndex = trackIndexs[i]; 94 | audioNode.connect(this.audioOutputNodes[trackIndex]); 95 | } 96 | }else { 97 | } 98 | } 99 | //TODO add test to make sure all id's for audio nodes stored in this.audioNodes exist in the current mediaSources, otherwise delete them. 100 | } 101 | } 102 | export default AudioManager; 103 | 104 | -------------------------------------------------------------------------------- /src/effect.js: -------------------------------------------------------------------------------- 1 | function refreshTextures(playlistEffectObject, textures, gl){ 2 | let textureOffset = 1; 3 | 4 | if (playlistEffectObject.parameters === undefined) return; 5 | 6 | let parameterKeys = Object.keys(playlistEffectObject.parameters); 7 | for (let i = 0; i < parameterKeys.length; i++) { 8 | let key = parameterKeys[i]; 9 | let parameter = playlistEffectObject.parameters[key]; 10 | if (typeof parameter !== "number"){ 11 | let texture = textures[textureOffset-1]; 12 | gl.activeTexture(gl.TEXTURE0 + textureOffset); 13 | gl.bindTexture(gl.TEXTURE_2D, texture); 14 | gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE); 15 | gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE); 16 | gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST); 17 | gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST); 18 | gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, parameter); 19 | textureOffset += 1; 20 | } 21 | } 22 | } 23 | 24 | function loadTextures(playlistEffectObject, gl){ 25 | if (playlistEffectObject.parameters === undefined) return []; 26 | let parameterKeys = Object.keys(playlistEffectObject.parameters); 27 | let textures = []; 28 | for (let i = 0; i < parameterKeys.length; i++) { 29 | let key = parameterKeys[i]; 30 | let parameter = playlistEffectObject.parameters[key]; 31 | if (typeof parameter !== "number"){ 32 | let texture = gl.createTexture(); 33 | textures.push(texture); 34 | } 35 | } 36 | refreshTextures(playlistEffectObject, textures, gl); 37 | return textures; 38 | } 39 | 40 | 41 | function compileShader(gl, shaderSource, shaderType) { 42 | let shader = gl.createShader(shaderType); 43 | gl.shaderSource(shader, shaderSource); 44 | gl.compileShader(shader); 45 | let success = gl.getShaderParameter(shader, gl.COMPILE_STATUS); 46 | if (!success) { 47 | throw "could not compile shader:" + gl.getShaderInfoLog(shader); 48 | } 49 | return shader; 50 | } 51 | 52 | 53 | function createShaderProgram(gl, vertexShaderSource, fragmentShaderSource){ 54 | let vertexShader = compileShader(gl, vertexShaderSource, gl.VERTEX_SHADER); 55 | let fragmentShader = compileShader(gl, fragmentShaderSource, gl.FRAGMENT_SHADER); 56 | let program = gl.createProgram(); 57 | 58 | gl.attachShader(program, vertexShader); 59 | gl.attachShader(program, fragmentShader); 60 | gl.linkProgram(program); 61 | 62 | if (!gl.getProgramParameter(program, gl.LINK_STATUS)){ 63 | throw {"error":4,"msg":"Can't link shader program for track", toString:function(){return this.msg;}}; 64 | } 65 | return program; 66 | } 67 | 68 | 69 | class Effect { 70 | constructor(playlistEffectObject, gl){ 71 | this.gl = gl; 72 | this.vertexShaderSrc = playlistEffectObject.effect.vertexShader; 73 | if (this.vertexShaderSrc === undefined){ 74 | this.vertexShaderSrc = "\ 75 | uniform float progress;\ 76 | uniform float duration;\ 77 | uniform vec2 source_resolution;\ 78 | uniform vec2 output_resolution;\ 79 | attribute vec2 a_position;\ 80 | attribute vec2 a_texCoord;\ 81 | varying vec2 v_texCoord;\ 82 | varying float v_progress;\ 83 | varying float v_duration;\ 84 | varying vec2 v_source_resolution;\ 85 | varying vec2 v_output_resolution;\ 86 | void main() {\ 87 | v_progress = progress;\ 88 | v_duration = duration;\ 89 | v_source_resolution = source_resolution;\ 90 | v_output_resolution = output_resolution;\ 91 | gl_Position = vec4(vec2(2.0,2.0)*a_position-vec2(1.0, 1.0), 0.0, 1.0);\ 92 | v_texCoord = a_texCoord;\ 93 | }"; 94 | } 95 | this.fragmentShaderSrc = playlistEffectObject.effect.fragmentShader; 96 | if (this.fragmentShaderSrc === undefined){ 97 | this.fragmentShaderSrc = "\ 98 | precision mediump float;\ 99 | uniform sampler2D u_image;\ 100 | varying vec2 v_texCoord;\ 101 | varying float v_progress;\ 102 | varying float v_duration;\ 103 | varying vec2 v_source_resolution;\ 104 | varying vec2 v_output_resolution;\ 105 | void main(){\ 106 | gl_FragColor = texture2D(u_image, v_texCoord);\ 107 | }"; 108 | } 109 | 110 | this.parameters = playlistEffectObject.parameters; 111 | if (this.parameters === undefined){ 112 | this.parameters = {}; 113 | } 114 | if (playlistEffectObject.effect.defaultParameters !== undefined){ 115 | for (let key in playlistEffectObject.effect.defaultParameters) { 116 | if (this.parameters[key]===undefined){ 117 | this.parameters[key] = playlistEffectObject.effect.defaultParameters[key]; 118 | } 119 | } 120 | } 121 | this.inputs = playlistEffectObject.inputs; 122 | if (this.inputs === undefined){ 123 | this.inputs = []; 124 | } 125 | 126 | this.textures = loadTextures(playlistEffectObject, this.gl); 127 | this.program = createShaderProgram(this.gl, this.vertexShaderSrc, this.fragmentShaderSrc); 128 | } 129 | 130 | 131 | update(playlistEffectObject){ 132 | refreshTextures(playlistEffectObject, this.textures, this.gl); 133 | this.inputs = playlistEffectObject.inputs; 134 | if (this.inputs === undefined){ 135 | this.inputs = []; 136 | } 137 | } 138 | 139 | } 140 | 141 | export default Effect; -------------------------------------------------------------------------------- /src/effectmanager.js: -------------------------------------------------------------------------------- 1 | import Effect from "./effect.js"; 2 | 3 | class EffectManager { 4 | constructor(gl){ 5 | this.effects = new Map(); 6 | this.gl = gl; 7 | //Setup the default effect 8 | this.newEffect("default",{"effect":{}}); 9 | } 10 | 11 | newEffect(id, playlistEffectObject){ 12 | //The playlist effect object is the representation of the effect stored in the playlist object 13 | let effect = new Effect(playlistEffectObject, this.gl); 14 | this.effects.set(id, effect); 15 | } 16 | 17 | updateEffects(playlistEffectObjects){ 18 | if (playlistEffectObjects === undefined) return; 19 | for (let key in playlistEffectObjects){ 20 | if (this.effects.has(key)){ 21 | //udpate the effect 22 | this.effects.get(key).update(playlistEffectObjects[key]); 23 | } else { 24 | //create the effect 25 | this.newEffect(key, playlistEffectObjects[key]); 26 | } 27 | } 28 | //TODO clean-up effects that don't exist 29 | } 30 | 31 | getEffectForInputId(inputId){ 32 | let effectIdList = this.effects.keys(); 33 | for (let key of effectIdList) { 34 | var effect = this.effects.get(key); 35 | if (effect.inputs.indexOf(inputId) > -1){ 36 | return effect; 37 | } 38 | } 39 | return this.effects.get("default"); 40 | } 41 | } 42 | 43 | export default EffectManager; 44 | -------------------------------------------------------------------------------- /src/sources/canvassource.js: -------------------------------------------------------------------------------- 1 | //Matthew Shotton, R&D User Experince,© BBC 2015 2 | import MediaSource from "./mediasource"; 3 | 4 | class CanvasSource extends MediaSource{ 5 | /** 6 | * Canvas playback source. Inherits from MediaSource 7 | * 8 | * A CanvasSource is the manifestation of a mediaSourceReference from a playlist object which has type "canvas". 9 | * 10 | * A CanvasSource exists for a period slightly before a CanvasSource is to play in order to give it time to preload and 11 | * is destroyed as soon as the CanvasSource has finished playing. 12 | * 13 | * @param {Object} properties - An object with the following attributes: id, duration, start, and element. 14 | * Where src is the URL of a video, or element is a DOM Video element. 15 | * 16 | * @param {WebGLContext} gl - a webGl context to render too. 17 | */ 18 | constructor(properties, gl){ 19 | super(properties, gl); 20 | this.width = properties.width; 21 | this.height = properties.height; 22 | } 23 | /** 24 | * Set the CanvasSource playing. 25 | */ 26 | play(){ 27 | super.play(); 28 | } 29 | /** 30 | * Seek to playlist time and do something appropriate with this CavnasSource. This can effect shaders applied to this 31 | * canvas and any MediaSourceListeners listening to the Id of this source. 32 | * @param {number} seekTime - The time to seek too, this is the overall time for the whole playlist. 33 | */ 34 | seek(time){ 35 | super.seek(time); 36 | } 37 | /** 38 | * Pause the CanvasSource if it is playing. 39 | */ 40 | pause(){ 41 | super.pause(); 42 | } 43 | /** 44 | * Set the CanvasSource loading, when it's ready isReady() will return true. 45 | */ 46 | load(){ 47 | //check if we're using an already instatiated element, if so don't do anything. 48 | if (super.load()){ 49 | this.seek(0); 50 | this.gl.texImage2D(this.gl.TEXTURE_2D, 0, this.gl.RGBA, this.gl.RGBA, this.gl.UNSIGNED_BYTE, this.element); 51 | this.ready = true; 52 | this.width = this.element.width; 53 | this.height = this.element.height; 54 | this.onready(this); 55 | return; 56 | } 57 | 58 | 59 | //otherwise begin the loading process for this mediaSource 60 | this.element = document.createElement("canvas"); 61 | this.element.width = this.width; 62 | this.element.height = this.height; 63 | this.gl.texImage2D(this.gl.TEXTURE_2D, 0, this.gl.RGBA, this.gl.RGBA, this.gl.UNSIGNED_BYTE, this.element); 64 | this.ready = true; 65 | this.onready(this); 66 | } 67 | render(program, renderParameters, textures){ 68 | super.render(program, renderParameters, textures); 69 | } 70 | } 71 | 72 | export default CanvasSource; -------------------------------------------------------------------------------- /src/sources/imagesource.js: -------------------------------------------------------------------------------- 1 | //Matthew Shotton, R&D User Experince,© BBC 2015 2 | import MediaSource from "./mediasource"; 3 | 4 | class ImageSource extends MediaSource{ 5 | /** 6 | * Image playback source. Inherits from MediaSource 7 | * 8 | * A ImageSource is the manifestation of a mediaSourceReference from a playlist object which has type "image". 9 | * 10 | * A ImageSource exists for a period slightly before a ImageSource is to play in order to give it time to preload and 11 | * is destroyed as soon as the ImageSource has finished playing. 12 | * 13 | * @param {Object} properties - An object with the following attributes: id, duration, start, and src or element. 14 | * Where src is the URL of a video, or element is a DOM Video element. 15 | * 16 | * @param {WebGLContext} gl - a webGl context to render too. 17 | */ 18 | constructor(properties, gl){ 19 | super(properties, gl); 20 | } 21 | /** 22 | * Set the ImageSource playing. 23 | */ 24 | play(){ 25 | super.play(); 26 | } 27 | /** 28 | * Seek to playlist time and do something appropriate with this ImageSource. This has little effect on the image as it's 29 | * static but can affect any effect shaders applied to this image and any MediaSourceListeners listening to the Id of 30 | * this source. 31 | * @param {number} seekTime - The time to seek too, this is the overall time for the whole playlist. 32 | */ 33 | seek(time){ 34 | super.seek(time); 35 | } 36 | /** 37 | * Pause the ImageSource if it is playing. 38 | */ 39 | pause(){ 40 | super.pause(); 41 | } 42 | /** 43 | * Set the ImageSource loading, when it's ready isReady() will return true. 44 | */ 45 | load(){ 46 | //check if we're using an already instatiated element, if so don't do anything. 47 | if (super.load()){ 48 | this.seek(0); 49 | this.gl.texImage2D(this.gl.TEXTURE_2D, 0, this.gl.RGBA, this.gl.RGBA, this.gl.UNSIGNED_BYTE, this.element); 50 | this.ready = true; 51 | // Upload the image into the texture. 52 | this.width = this.element.width; 53 | this.height = this.element.height; 54 | this.onready(this); 55 | return; 56 | } 57 | 58 | //otherwise begin the loading process for this mediaSource 59 | this.element = new Image(); 60 | let _this = this; 61 | this.element.onload = function(){ 62 | _this.gl.texImage2D(_this.gl.TEXTURE_2D, 0, _this.gl.RGBA, _this.gl.RGBA, _this.gl.UNSIGNED_BYTE, _this.element); 63 | _this.ready = true; 64 | _this.onready(_this); 65 | _this.width = _this.element.width; 66 | _this.height = _this.element.height; 67 | }; 68 | this.element.src = this.src; 69 | } 70 | render(program, renderParameters, textures){ 71 | super.render(program, renderParameters, textures); 72 | } 73 | } 74 | 75 | export default ImageSource; -------------------------------------------------------------------------------- /src/sources/mediasource.js: -------------------------------------------------------------------------------- 1 | //Matthew Shotton, R&D User Experince,© BBC 2015 2 | 3 | class MediaSource { 4 | /** 5 | * Parent class of all MediaSources 6 | * 7 | * A MediaSource is the manifestation of a mediaSourceReference from a playlist object. It typically contains the 8 | * original DOM element to be composited along with a number of functions to load, play, pause, seek and render that 9 | * element to the webgl context. 10 | * 11 | * A MediaSource exists for a period slightly before a MediaSource is to play in order to give it time to preload and 12 | * is destroyed as soon as the MediaSource has finished playing. 13 | * 14 | * @param {Object} properties - An object with the following attributes: id, duration, start, and src or element. 15 | * Where src is the URL of something that can be used to create a DOM element that can be rendered to canvas, or 16 | * element is a DOM element that can be rendered to a canvas. 17 | * 18 | * @param {WebGLContext} gl - a webGl context to render too. 19 | */ 20 | constructor(properties, gl){ 21 | this.gl = gl; 22 | this.id = properties.id; 23 | this.duration = properties.duration; 24 | this.start = properties.start; 25 | this.playing = false; 26 | this.ready = false; 27 | this.element = undefined; 28 | this.src = undefined; 29 | this.texture = undefined; 30 | this.width = undefined; 31 | this.height = undefined; 32 | this.mediaSourceListeners = []; 33 | 34 | this.disposeOfElementOnDestroy = false; 35 | 36 | //If the mediaSource is created from a src string then it must be resonsible for cleaning itself up. 37 | if (properties.src !== undefined){ 38 | this.disposeOfElementOnDestroy = true; 39 | this.src = properties.src; 40 | }else { 41 | //If the MediaSource is created from an element then it should not clean the element up on destruction as it may be used elsewhere. 42 | this.disposeOfElementOnDestroy = false; 43 | this.element = properties.element; 44 | } 45 | 46 | 47 | /*let positionLocation = gl.getAttribLocation(program, "a_position"); 48 | let texCoordLocation = gl.getAttribLocation(program, "a_texCoord");*/ 49 | 50 | //Hard Code these for now, but this is baaaaaad 51 | let positionLocation = 0; 52 | let texCoordLocation = 1; 53 | 54 | gl.blendFunc(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA); 55 | gl.enable ( gl.BLEND) ; 56 | // Create a texture. 57 | this.texture = gl.createTexture(); 58 | gl.bindTexture(gl.TEXTURE_2D, this.texture); 59 | gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, true); 60 | // Set the parameters so we can render any size image. 61 | gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE); 62 | gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE); 63 | gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST); 64 | gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST); 65 | 66 | 67 | let buffer = gl.createBuffer(); 68 | gl.bindBuffer(gl.ARRAY_BUFFER, buffer); 69 | gl.enableVertexAttribArray(positionLocation); 70 | gl.vertexAttribPointer(positionLocation, 2, gl.FLOAT, false, 0, 0); 71 | 72 | gl.bufferData( 73 | gl.ARRAY_BUFFER, 74 | /*new Float32Array([ 75 | 1.0, 1.0, 76 | -1.0, 1.0, 77 | 1.0, -1.0, 78 | 1.0, -1.0, 79 | -1.0, 1.0, 80 | -1.0, -1.0]),*/ 81 | new Float32Array([ 82 | 1.0, 1.0, 83 | 0.0, 1.0, 84 | 1.0, 0.0, 85 | 1.0, 0.0, 86 | 0.0, 1.0, 87 | 0.0, 0.0]), 88 | gl.STATIC_DRAW); 89 | gl.enableVertexAttribArray(texCoordLocation); 90 | gl.vertexAttribPointer(texCoordLocation, 2, gl.FLOAT, false, 0, 0); 91 | 92 | } 93 | 94 | /** 95 | * Set the MediaSource playing. 96 | */ 97 | play(){ 98 | //console.log("Playing", this.id); 99 | if (this.playing === false){ 100 | for (let i = 0; i < this.mediaSourceListeners.length; i++) { 101 | if(typeof this.mediaSourceListeners[i].play === 'function')this.mediaSourceListeners[i].play(this); 102 | } 103 | } 104 | this.playing = true; 105 | } 106 | /** 107 | * Pause the MediaSource if it is playing. 108 | */ 109 | pause(){ 110 | console.debug("Pausing", this.id); 111 | this.playing = false; 112 | for (let i = 0; i < this.mediaSourceListeners.length; i++) { 113 | if(typeof this.mediaSourceListeners[i].pause === 'function')this.mediaSourceListeners[i].pause(this); 114 | } 115 | } 116 | /** 117 | * Seek the MediaSource to an appropriate point for the passed time. 118 | * @param {number} seekTime - The time to seek too, this is the overall time for the whole playlist. 119 | */ 120 | seek(seekTime){ 121 | //this.currentTime = seekTime; 122 | for (let i = 0; i < this.mediaSourceListeners.length; i++) { 123 | if(typeof this.mediaSourceListeners[i].seek === 'function')this.mediaSourceListeners[i].seek(this, seekTime); 124 | } 125 | } 126 | /** 127 | * Check if the MediaSource is ready to start playing. 128 | */ 129 | isReady(){ 130 | let listenerReady = true; 131 | for (let i = 0; i < this.mediaSourceListeners.length; i++) { 132 | if(typeof this.mediaSourceListeners[i].isReady === 'function'){ 133 | if (this.mediaSourceListeners[i].isReady(this) === false){ 134 | listenerReady = false; 135 | } 136 | } 137 | } 138 | if (listenerReady === true && this.ready === true) return true; 139 | return false; 140 | } 141 | /** 142 | * Set the MediaSource loading, when it's ready isReady() will return true. 143 | */ 144 | load(){ 145 | console.debug("Loading", this.id); 146 | for (let i = 0; i < this.mediaSourceListeners.length; i++) { 147 | if(typeof this.mediaSourceListeners[i].load === 'function')this.mediaSourceListeners[i].load(this); 148 | } 149 | if (this.element !== undefined) { 150 | return true; 151 | } 152 | return false; 153 | } 154 | /** 155 | * Clean up the MediaSource for detruction. 156 | */ 157 | destroy(){ 158 | console.debug("Destroying", this.id); 159 | for (let i = 0; i < this.mediaSourceListeners.length; i++) { 160 | if (typeof this.mediaSourceListeners[i].destroy === 'function') this.mediaSourceListeners[i].destroy(this); 161 | } 162 | if (this.disposeOfElementOnDestroy){ 163 | delete this.element; 164 | } 165 | } 166 | /** 167 | * Render the MediaSource to the WebGL context passed into the constructor. 168 | */ 169 | render(program, renderParameters, textures){ 170 | //renders the media source to the WebGL context using the pased program 171 | let overriddenElement; 172 | for (let i = 0; i < this.mediaSourceListeners.length; i++) { 173 | if (typeof this.mediaSourceListeners[i].render === 'function'){ 174 | let result = this.mediaSourceListeners[i].render(this, renderParameters); 175 | if (result !== undefined) overriddenElement = result; 176 | } 177 | } 178 | 179 | this.gl.useProgram(program); 180 | let renderParametersKeys = Object.keys(renderParameters); 181 | let textureOffset = 1; 182 | for (let index in renderParametersKeys){ 183 | let key = renderParametersKeys[index]; 184 | let parameterLoctation = this.gl.getUniformLocation(program, key); 185 | if (parameterLoctation !== -1){ 186 | if (typeof renderParameters[key] === "number"){ 187 | this.gl.uniform1f(parameterLoctation, renderParameters[key]); 188 | } 189 | else if( Object.prototype.toString.call(renderParameters[key]) === '[object Array]'){ 190 | let array = renderParameters[key]; 191 | if(array.length === 1){ 192 | this.gl.uniform1fv(parameterLoctation, array); 193 | } else if(array.length === 2){ 194 | this.gl.uniform2fv(parameterLoctation, array); 195 | } else if(array.length === 3){ 196 | this.gl.uniform3fv(parameterLoctation, array); 197 | } else if(array.length === 4){ 198 | this.gl.uniform4fv(parameterLoctation, array); 199 | } else{ 200 | console.debug("Shader parameter", key, "is too long and array:", array); 201 | } 202 | } 203 | else{ 204 | //Is a texture 205 | this.gl.activeTexture(this.gl.TEXTURE0 + textureOffset); 206 | this.gl.uniform1i(parameterLoctation, textureOffset); 207 | this.gl.bindTexture(this.gl.TEXTURE_2D, textures[textureOffset-1]); 208 | } 209 | } 210 | } 211 | 212 | this.gl.activeTexture(this.gl.TEXTURE0); 213 | let textureLocation = this.gl.getUniformLocation(program, "u_image"); 214 | this.gl.uniform1i(textureLocation, 0); 215 | this.gl.bindTexture(this.gl.TEXTURE_2D, this.texture); 216 | if (overriddenElement !== undefined){ 217 | this.gl.texImage2D(this.gl.TEXTURE_2D, 0, this.gl.RGBA, this.gl.RGBA, this.gl.UNSIGNED_BYTE, overriddenElement); 218 | } else { 219 | this.gl.texImage2D(this.gl.TEXTURE_2D, 0, this.gl.RGBA, this.gl.RGBA, this.gl.UNSIGNED_BYTE, this.element); 220 | } 221 | this.gl.drawArrays(this.gl.TRIANGLES, 0, 6); 222 | } 223 | onready(mediaSource){ 224 | } 225 | } 226 | 227 | export default MediaSource; -------------------------------------------------------------------------------- /src/sources/videosource.js: -------------------------------------------------------------------------------- 1 | //Matthew Shotton, R&D User Experince,© BBC 2015 2 | import MediaSource from "./mediasource"; 3 | 4 | 5 | function eventOneTime(element, type, callback){ 6 | let handleEvent = function(e){ 7 | e.target.removeEventListener(e.type, handleEvent); 8 | return callback(e); 9 | }; 10 | 11 | element.addEventListener(type, handleEvent, false); 12 | } 13 | 14 | 15 | 16 | class VideoSource extends MediaSource{ 17 | /** 18 | * Video playback source. Inherits from MediaSource 19 | * 20 | * A VideoSource is the manifestation of a mediaSourceReference from a playlist object which has type "video". 21 | * 22 | * A VideoSource exists for a period slightly before a VideoSource is to play in order to give it time to preload and 23 | * is destroyed as soon as the VideoSource has finished playing. You can define an offset into the original video to 24 | * start playing by passing in a sourceStart value in the properties. 25 | * 26 | * @param {Object} properties - An object with the following attributes: id, duration, start, sourceStart, and src or element. 27 | * Where src is the URL of a video, or element is a DOM Video element. 28 | * 29 | * @param {WebGLContext} gl - a webGl context to render too. 30 | */ 31 | constructor(properties, gl){ 32 | super(properties, gl); 33 | this.sourceStart = 0; 34 | this._volume = 1.0; 35 | if (properties.sourceStart !== undefined){ 36 | this.sourceStart = properties.sourceStart; 37 | } 38 | if (properties.volume !== undefined){ 39 | this._volume = properties.volume; 40 | } 41 | } 42 | /** 43 | * Set the VideoSource playing. 44 | */ 45 | play(){ 46 | super.play(); 47 | let _this = this; 48 | 49 | let playVideo = function(){ 50 | if (_this.element.readyState > 3){ 51 | _this.ready = true; 52 | _this.element.play(); 53 | } else { 54 | console.debug("Can't play video due to readyState"); 55 | _this.ready = false; 56 | eventOneTime(_this.element, "canplay", playVideo); 57 | } 58 | }; 59 | 60 | playVideo(); 61 | } 62 | /** 63 | * Seek the VideoSource to an appropriate point for the passed time. 64 | * @param {number} seekTime - The time to seek too, this is the overall time for the whole playlist. 65 | */ 66 | seek(time){ 67 | super.seek(); 68 | let _this = this; 69 | 70 | let seekVideo = function(){ 71 | if (_this.element.readyState > 3){ 72 | _this.ready = true; 73 | if ((time - _this.start) < 0 || time >(_this.start+_this.duration)){ 74 | _this.element.currentTime = _this.sourceStart; 75 | } else { 76 | _this.element.currentTime = (time - _this.start) + _this.sourceStart; 77 | } 78 | } else { 79 | //If the element isn't ready to seek create a one-time event which seeks the element once it is ready. 80 | console.debug("Can't seek video due to readyState"); 81 | _this.ready = false; 82 | eventOneTime(_this.element, "canplay", seekVideo); 83 | } 84 | }; 85 | 86 | seekVideo(); 87 | } 88 | /** 89 | * Pause the VideoSource if it is playing. 90 | */ 91 | pause(){ 92 | super.pause(); 93 | this.element.pause(); 94 | } 95 | /** 96 | * Set the VideoSource loading, when it's ready isReady() will return true. 97 | */ 98 | load(){ 99 | //check if we're using an already instatiated element, if so don't do anything. 100 | 101 | if (super.load()){ 102 | //this.element.currentTime = this.sourceStart; 103 | this.seek(0); 104 | this.gl.texImage2D(this.gl.TEXTURE_2D, 0, this.gl.RGBA, this.gl.RGBA, this.gl.UNSIGNED_BYTE, this.element); 105 | this.ready = true; 106 | this.width = this.element.videoWidth; 107 | this.height = this.element.videoHeight; 108 | this.onready(this); 109 | return; 110 | } 111 | //otherwise begin the loading process for this mediaSource 112 | this.element = document.createElement('video'); 113 | this.element.setAttribute("crossorigin", "anonymous"); 114 | //construct a fragement URL to cut the required segment from the source video 115 | this.element.src = this.src; 116 | this.element.volume = this._volume; 117 | this.element.preload = "auto"; 118 | this.element.load(); 119 | let _this = this; 120 | this.element.addEventListener('loadeddata', function() { 121 | _this.element.currentTime = _this.sourceStart; 122 | _this.seek(0); 123 | _this.gl.texImage2D(_this.gl.TEXTURE_2D, 0, _this.gl.RGBA, _this.gl.RGBA, _this.gl.UNSIGNED_BYTE, _this.element); 124 | _this.ready = true; 125 | _this.width = _this.element.videoWidth; 126 | _this.height = _this.element.videoHeight; 127 | _this.onready(_this); 128 | }, false); 129 | /*this.element.addEventListener('seeked', function(){ 130 | console.log("SEEKED"); 131 | _this.ready = true; 132 | _this.onready(_this); 133 | })*/ 134 | 135 | 136 | } 137 | /** 138 | * Render the VideoSource to the WebGL context passed into the constructor. 139 | */ 140 | render(program, renderParameters, textures){ 141 | this.element.playbackRate = renderParameters["playback_rate"]; 142 | super.render(program, renderParameters, textures); 143 | } 144 | /** 145 | * Clean up the VideoSource for detruction. 146 | */ 147 | destroy(){ 148 | this.element.pause(); 149 | if (this.disposeOfElementOnDestroy){ 150 | this.element.src = ""; 151 | this.element.removeAttribute("src"); 152 | } 153 | super.destroy(); 154 | } 155 | } 156 | 157 | export default VideoSource; -------------------------------------------------------------------------------- /tutorial/1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bbc/html5-video-compositor/6dcffdf315e3a6aaa4fff5526c164676a307742b/tutorial/1.png -------------------------------------------------------------------------------- /tutorial/2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bbc/html5-video-compositor/6dcffdf315e3a6aaa4fff5526c164676a307742b/tutorial/2.png -------------------------------------------------------------------------------- /tutorial/3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bbc/html5-video-compositor/6dcffdf315e3a6aaa4fff5526c164676a307742b/tutorial/3.png -------------------------------------------------------------------------------- /tutorial/4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bbc/html5-video-compositor/6dcffdf315e3a6aaa4fff5526c164676a307742b/tutorial/4.png -------------------------------------------------------------------------------- /tutorial/5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bbc/html5-video-compositor/6dcffdf315e3a6aaa4fff5526c164676a307742b/tutorial/5.png -------------------------------------------------------------------------------- /tutorial/6.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bbc/html5-video-compositor/6dcffdf315e3a6aaa4fff5526c164676a307742b/tutorial/6.png -------------------------------------------------------------------------------- /webpack.commonjs2.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | entry: __dirname + "/src/main.js", 3 | output: { 4 | path: __dirname+'/dist', 5 | filename: "videocompositor.commonjs2.js", 6 | libraryTarget: "commonjs2", 7 | library: "VideoCompositor" 8 | }, 9 | module: { 10 | loaders: [ 11 | { test: /\.css$/, loader: "style!css" }, 12 | { test: /\.js$/, exclude: /node_modules/, loader: "babel-loader"} 13 | ] 14 | } 15 | }; -------------------------------------------------------------------------------- /webpack.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | entry: __dirname + "/src/main.js", 3 | output: { 4 | path: __dirname+'/dist', 5 | filename: "videocompositor.js", 6 | libraryTarget: "var", 7 | library: "VideoCompositor" 8 | }, 9 | module: { 10 | loaders: [ 11 | { test: /\.css$/, loader: "style!css" }, 12 | { test: /\.js$/, exclude: /node_modules/, loader: "babel-loader"} 13 | ] 14 | } 15 | }; 16 | --------------------------------------------------------------------------------