├── .gitignore ├── LICENSE ├── README.md ├── WebExtension ├── common.js ├── data │ ├── controls.js │ ├── css.js │ ├── icons │ │ ├── 16.png │ │ ├── 32.png │ │ ├── 48.png │ │ └── 64.png │ ├── next_track.js │ ├── no_buffer.js │ ├── page.js │ ├── popup │ │ ├── history │ │ │ ├── index.html │ │ │ ├── index.js │ │ │ └── loading.gif │ │ ├── icons │ │ │ ├── back.svg │ │ │ ├── backward.svg │ │ │ ├── checked.png │ │ │ ├── cover.jpg │ │ │ ├── for.svg │ │ │ ├── forward.svg │ │ │ ├── history.svg │ │ │ ├── pause.svg │ │ │ ├── play.svg │ │ │ ├── settings.svg │ │ │ ├── stop.svg │ │ │ └── uchecked.png │ │ ├── index.css │ │ ├── index.html │ │ └── index.js │ └── quality.js └── manifest.json ├── XUL ├── builds │ ├── 0.3.6.xpi │ ├── 0.3.6b1.xpi │ ├── 0.4.0.xpi │ └── 0.4.1.xpi └── src │ ├── .jpmignore │ ├── data │ ├── end.js │ ├── icon-16.png │ ├── icon-32.png │ ├── notification.png │ ├── overlay.css │ └── player │ │ ├── close.png │ │ ├── controls-12.png │ │ ├── controls-32.png │ │ ├── controls-48.png │ │ ├── grippy_large.png │ │ ├── history.png │ │ ├── load.gif │ │ ├── mp.css │ │ ├── mp.html │ │ ├── mp.js │ │ └── search.png │ ├── icon.png │ ├── icon64.png │ ├── iyccenter.xpi │ ├── lib │ ├── config.js │ ├── http.js │ ├── main.js │ ├── storage.js │ ├── toolbarbutton │ │ ├── new.js │ │ └── old.js │ └── userstyles.js │ ├── locale │ ├── en-US.properties │ └── pl-PL.properties │ └── package.json └── drawings ├── icon.cdr ├── pause.cdr ├── play.cdr └── stop.cdr /.gitignore: -------------------------------------------------------------------------------- 1 | addon-sdk* 2 | node_modules/ 3 | test/ 4 | .DS_Store 5 | Thumbs.db 6 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MOZILLA PUBLIC LICENSE 2 | Version 1.1 3 | 4 | --------------- 5 | 6 | 1. Definitions. 7 | 8 | 1.0.1. "Commercial Use" means distribution or otherwise making the 9 | Covered Code available to a third party. 10 | 11 | 1.1. "Contributor" means each entity that creates or contributes to 12 | the creation of Modifications. 13 | 14 | 1.2. "Contributor Version" means the combination of the Original 15 | Code, prior Modifications used by a Contributor, and the Modifications 16 | made by that particular Contributor. 17 | 18 | 1.3. "Covered Code" means the Original Code or Modifications or the 19 | combination of the Original Code and Modifications, in each case 20 | including portions thereof. 21 | 22 | 1.4. "Electronic Distribution Mechanism" means a mechanism generally 23 | accepted in the software development community for the electronic 24 | transfer of data. 25 | 26 | 1.5. "Executable" means Covered Code in any form other than Source 27 | Code. 28 | 29 | 1.6. "Initial Developer" means the individual or entity identified 30 | as the Initial Developer in the Source Code notice required by Exhibit 31 | A. 32 | 33 | 1.7. "Larger Work" means a work which combines Covered Code or 34 | portions thereof with code not governed by the terms of this License. 35 | 36 | 1.8. "License" means this document. 37 | 38 | 1.8.1. "Licensable" means having the right to grant, to the maximum 39 | extent possible, whether at the time of the initial grant or 40 | subsequently acquired, any and all of the rights conveyed herein. 41 | 42 | 1.9. "Modifications" means any addition to or deletion from the 43 | substance or structure of either the Original Code or any previous 44 | Modifications. When Covered Code is released as a series of files, a 45 | Modification is: 46 | A. Any addition to or deletion from the contents of a file 47 | containing Original Code or previous Modifications. 48 | 49 | B. Any new file that contains any part of the Original Code or 50 | previous Modifications. 51 | 52 | 1.10. "Original Code" means Source Code of computer software code 53 | which is described in the Source Code notice required by Exhibit A as 54 | Original Code, and which, at the time of its release under this 55 | License is not already Covered Code governed by this License. 56 | 57 | 1.10.1. "Patent Claims" means any patent claim(s), now owned or 58 | hereafter acquired, including without limitation, method, process, 59 | and apparatus claims, in any patent Licensable by grantor. 60 | 61 | 1.11. "Source Code" means the preferred form of the Covered Code for 62 | making modifications to it, including all modules it contains, plus 63 | any associated interface definition files, scripts used to control 64 | compilation and installation of an Executable, or source code 65 | differential comparisons against either the Original Code or another 66 | well known, available Covered Code of the Contributor's choice. The 67 | Source Code can be in a compressed or archival form, provided the 68 | appropriate decompression or de-archiving software is widely available 69 | for no charge. 70 | 71 | 1.12. "You" (or "Your") means an individual or a legal entity 72 | exercising rights under, and complying with all of the terms of, this 73 | License or a future version of this License issued under Section 6.1. 74 | For legal entities, "You" includes any entity which controls, is 75 | controlled by, or is under common control with You. For purposes of 76 | this definition, "control" means (a) the power, direct or indirect, 77 | to cause the direction or management of such entity, whether by 78 | contract or otherwise, or (b) ownership of more than fifty percent 79 | (50%) of the outstanding shares or beneficial ownership of such 80 | entity. 81 | 82 | 2. Source Code License. 83 | 84 | 2.1. The Initial Developer Grant. 85 | The Initial Developer hereby grants You a world-wide, royalty-free, 86 | non-exclusive license, subject to third party intellectual property 87 | claims: 88 | (a) under intellectual property rights (other than patent or 89 | trademark) Licensable by Initial Developer to use, reproduce, 90 | modify, display, perform, sublicense and distribute the Original 91 | Code (or portions thereof) with or without Modifications, and/or 92 | as part of a Larger Work; and 93 | 94 | (b) under Patents Claims infringed by the making, using or 95 | selling of Original Code, to make, have made, use, practice, 96 | sell, and offer for sale, and/or otherwise dispose of the 97 | Original Code (or portions thereof). 98 | 99 | (c) the licenses granted in this Section 2.1(a) and (b) are 100 | effective on the date Initial Developer first distributes 101 | Original Code under the terms of this License. 102 | 103 | (d) Notwithstanding Section 2.1(b) above, no patent license is 104 | granted: 1) for code that You delete from the Original Code; 2) 105 | separate from the Original Code; or 3) for infringements caused 106 | by: i) the modification of the Original Code or ii) the 107 | combination of the Original Code with other software or devices. 108 | 109 | 2.2. Contributor Grant. 110 | Subject to third party intellectual property claims, each Contributor 111 | hereby grants You a world-wide, royalty-free, non-exclusive license 112 | 113 | (a) under intellectual property rights (other than patent or 114 | trademark) Licensable by Contributor, to use, reproduce, modify, 115 | display, perform, sublicense and distribute the Modifications 116 | created by such Contributor (or portions thereof) either on an 117 | unmodified basis, with other Modifications, as Covered Code 118 | and/or as part of a Larger Work; and 119 | 120 | (b) under Patent Claims infringed by the making, using, or 121 | selling of Modifications made by that Contributor either alone 122 | and/or in combination with its Contributor Version (or portions 123 | of such combination), to make, use, sell, offer for sale, have 124 | made, and/or otherwise dispose of: 1) Modifications made by that 125 | Contributor (or portions thereof); and 2) the combination of 126 | Modifications made by that Contributor with its Contributor 127 | Version (or portions of such combination). 128 | 129 | (c) the licenses granted in Sections 2.2(a) and 2.2(b) are 130 | effective on the date Contributor first makes Commercial Use of 131 | the Covered Code. 132 | 133 | (d) Notwithstanding Section 2.2(b) above, no patent license is 134 | granted: 1) for any code that Contributor has deleted from the 135 | Contributor Version; 2) separate from the Contributor Version; 136 | 3) for infringements caused by: i) third party modifications of 137 | Contributor Version or ii) the combination of Modifications made 138 | by that Contributor with other software (except as part of the 139 | Contributor Version) or other devices; or 4) under Patent Claims 140 | infringed by Covered Code in the absence of Modifications made by 141 | that Contributor. 142 | 143 | 3. Distribution Obligations. 144 | 145 | 3.1. Application of License. 146 | The Modifications which You create or to which You contribute are 147 | governed by the terms of this License, including without limitation 148 | Section 2.2. The Source Code version of Covered Code may be 149 | distributed only under the terms of this License or a future version 150 | of this License released under Section 6.1, and You must include a 151 | copy of this License with every copy of the Source Code You 152 | distribute. You may not offer or impose any terms on any Source Code 153 | version that alters or restricts the applicable version of this 154 | License or the recipients' rights hereunder. However, You may include 155 | an additional document offering the additional rights described in 156 | Section 3.5. 157 | 158 | 3.2. Availability of Source Code. 159 | Any Modification which You create or to which You contribute must be 160 | made available in Source Code form under the terms of this License 161 | either on the same media as an Executable version or via an accepted 162 | Electronic Distribution Mechanism to anyone to whom you made an 163 | Executable version available; and if made available via Electronic 164 | Distribution Mechanism, must remain available for at least twelve (12) 165 | months after the date it initially became available, or at least six 166 | (6) months after a subsequent version of that particular Modification 167 | has been made available to such recipients. You are responsible for 168 | ensuring that the Source Code version remains available even if the 169 | Electronic Distribution Mechanism is maintained by a third party. 170 | 171 | 3.3. Description of Modifications. 172 | You must cause all Covered Code to which You contribute to contain a 173 | file documenting the changes You made to create that Covered Code and 174 | the date of any change. You must include a prominent statement that 175 | the Modification is derived, directly or indirectly, from Original 176 | Code provided by the Initial Developer and including the name of the 177 | Initial Developer in (a) the Source Code, and (b) in any notice in an 178 | Executable version or related documentation in which You describe the 179 | origin or ownership of the Covered Code. 180 | 181 | 3.4. Intellectual Property Matters 182 | (a) Third Party Claims. 183 | If Contributor has knowledge that a license under a third party's 184 | intellectual property rights is required to exercise the rights 185 | granted by such Contributor under Sections 2.1 or 2.2, 186 | Contributor must include a text file with the Source Code 187 | distribution titled "LEGAL" which describes the claim and the 188 | party making the claim in sufficient detail that a recipient will 189 | know whom to contact. If Contributor obtains such knowledge after 190 | the Modification is made available as described in Section 3.2, 191 | Contributor shall promptly modify the LEGAL file in all copies 192 | Contributor makes available thereafter and shall take other steps 193 | (such as notifying appropriate mailing lists or newsgroups) 194 | reasonably calculated to inform those who received the Covered 195 | Code that new knowledge has been obtained. 196 | 197 | (b) Contributor APIs. 198 | If Contributor's Modifications include an application programming 199 | interface and Contributor has knowledge of patent licenses which 200 | are reasonably necessary to implement that API, Contributor must 201 | also include this information in the LEGAL file. 202 | 203 | (c) Representations. 204 | Contributor represents that, except as disclosed pursuant to 205 | Section 3.4(a) above, Contributor believes that Contributor's 206 | Modifications are Contributor's original creation(s) and/or 207 | Contributor has sufficient rights to grant the rights conveyed by 208 | this License. 209 | 210 | 3.5. Required Notices. 211 | You must duplicate the notice in Exhibit A in each file of the Source 212 | Code. If it is not possible to put such notice in a particular Source 213 | Code file due to its structure, then You must include such notice in a 214 | location (such as a relevant directory) where a user would be likely 215 | to look for such a notice. If You created one or more Modification(s) 216 | You may add your name as a Contributor to the notice described in 217 | Exhibit A. You must also duplicate this License in any documentation 218 | for the Source Code where You describe recipients' rights or ownership 219 | rights relating to Covered Code. You may choose to offer, and to 220 | charge a fee for, warranty, support, indemnity or liability 221 | obligations to one or more recipients of Covered Code. However, You 222 | may do so only on Your own behalf, and not on behalf of the Initial 223 | Developer or any Contributor. You must make it absolutely clear than 224 | any such warranty, support, indemnity or liability obligation is 225 | offered by You alone, and You hereby agree to indemnify the Initial 226 | Developer and every Contributor for any liability incurred by the 227 | Initial Developer or such Contributor as a result of warranty, 228 | support, indemnity or liability terms You offer. 229 | 230 | 3.6. Distribution of Executable Versions. 231 | You may distribute Covered Code in Executable form only if the 232 | requirements of Section 3.1-3.5 have been met for that Covered Code, 233 | and if You include a notice stating that the Source Code version of 234 | the Covered Code is available under the terms of this License, 235 | including a description of how and where You have fulfilled the 236 | obligations of Section 3.2. The notice must be conspicuously included 237 | in any notice in an Executable version, related documentation or 238 | collateral in which You describe recipients' rights relating to the 239 | Covered Code. You may distribute the Executable version of Covered 240 | Code or ownership rights under a license of Your choice, which may 241 | contain terms different from this License, provided that You are in 242 | compliance with the terms of this License and that the license for the 243 | Executable version does not attempt to limit or alter the recipient's 244 | rights in the Source Code version from the rights set forth in this 245 | License. If You distribute the Executable version under a different 246 | license You must make it absolutely clear that any terms which differ 247 | from this License are offered by You alone, not by the Initial 248 | Developer or any Contributor. You hereby agree to indemnify the 249 | Initial Developer and every Contributor for any liability incurred by 250 | the Initial Developer or such Contributor as a result of any such 251 | terms You offer. 252 | 253 | 3.7. Larger Works. 254 | You may create a Larger Work by combining Covered Code with other code 255 | not governed by the terms of this License and distribute the Larger 256 | Work as a single product. In such a case, You must make sure the 257 | requirements of this License are fulfilled for the Covered Code. 258 | 259 | 4. Inability to Comply Due to Statute or Regulation. 260 | 261 | If it is impossible for You to comply with any of the terms of this 262 | License with respect to some or all of the Covered Code due to 263 | statute, judicial order, or regulation then You must: (a) comply with 264 | the terms of this License to the maximum extent possible; and (b) 265 | describe the limitations and the code they affect. Such description 266 | must be included in the LEGAL file described in Section 3.4 and must 267 | be included with all distributions of the Source Code. Except to the 268 | extent prohibited by statute or regulation, such description must be 269 | sufficiently detailed for a recipient of ordinary skill to be able to 270 | understand it. 271 | 272 | 5. Application of this License. 273 | 274 | This License applies to code to which the Initial Developer has 275 | attached the notice in Exhibit A and to related Covered Code. 276 | 277 | 6. Versions of the License. 278 | 279 | 6.1. New Versions. 280 | Netscape Communications Corporation ("Netscape") may publish revised 281 | and/or new versions of the License from time to time. Each version 282 | will be given a distinguishing version number. 283 | 284 | 6.2. Effect of New Versions. 285 | Once Covered Code has been published under a particular version of the 286 | License, You may always continue to use it under the terms of that 287 | version. You may also choose to use such Covered Code under the terms 288 | of any subsequent version of the License published by Netscape. No one 289 | other than Netscape has the right to modify the terms applicable to 290 | Covered Code created under this License. 291 | 292 | 6.3. Derivative Works. 293 | If You create or use a modified version of this License (which you may 294 | only do in order to apply it to code which is not already Covered Code 295 | governed by this License), You must (a) rename Your license so that 296 | the phrases "Mozilla", "MOZILLAPL", "MOZPL", "Netscape", 297 | "MPL", "NPL" or any confusingly similar phrase do not appear in your 298 | license (except to note that your license differs from this License) 299 | and (b) otherwise make it clear that Your version of the license 300 | contains terms which differ from the Mozilla Public License and 301 | Netscape Public License. (Filling in the name of the Initial 302 | Developer, Original Code or Contributor in the notice described in 303 | Exhibit A shall not of themselves be deemed to be modifications of 304 | this License.) 305 | 306 | 7. DISCLAIMER OF WARRANTY. 307 | 308 | COVERED CODE IS PROVIDED UNDER THIS LICENSE ON AN "AS IS" BASIS, 309 | WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, 310 | WITHOUT LIMITATION, WARRANTIES THAT THE COVERED CODE IS FREE OF 311 | DEFECTS, MERCHANTABLE, FIT FOR A PARTICULAR PURPOSE OR NON-INFRINGING. 312 | THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE COVERED CODE 313 | IS WITH YOU. SHOULD ANY COVERED CODE PROVE DEFECTIVE IN ANY RESPECT, 314 | YOU (NOT THE INITIAL DEVELOPER OR ANY OTHER CONTRIBUTOR) ASSUME THE 315 | COST OF ANY NECESSARY SERVICING, REPAIR OR CORRECTION. THIS DISCLAIMER 316 | OF WARRANTY CONSTITUTES AN ESSENTIAL PART OF THIS LICENSE. NO USE OF 317 | ANY COVERED CODE IS AUTHORIZED HEREUNDER EXCEPT UNDER THIS DISCLAIMER. 318 | 319 | 8. TERMINATION. 320 | 321 | 8.1. This License and the rights granted hereunder will terminate 322 | automatically if You fail to comply with terms herein and fail to cure 323 | such breach within 30 days of becoming aware of the breach. All 324 | sublicenses to the Covered Code which are properly granted shall 325 | survive any termination of this License. Provisions which, by their 326 | nature, must remain in effect beyond the termination of this License 327 | shall survive. 328 | 329 | 8.2. If You initiate litigation by asserting a patent infringement 330 | claim (excluding declatory judgment actions) against Initial Developer 331 | or a Contributor (the Initial Developer or Contributor against whom 332 | You file such action is referred to as "Participant") alleging that: 333 | 334 | (a) such Participant's Contributor Version directly or indirectly 335 | infringes any patent, then any and all rights granted by such 336 | Participant to You under Sections 2.1 and/or 2.2 of this License 337 | shall, upon 60 days notice from Participant terminate prospectively, 338 | unless if within 60 days after receipt of notice You either: (i) 339 | agree in writing to pay Participant a mutually agreeable reasonable 340 | royalty for Your past and future use of Modifications made by such 341 | Participant, or (ii) withdraw Your litigation claim with respect to 342 | the Contributor Version against such Participant. If within 60 days 343 | of notice, a reasonable royalty and payment arrangement are not 344 | mutually agreed upon in writing by the parties or the litigation claim 345 | is not withdrawn, the rights granted by Participant to You under 346 | Sections 2.1 and/or 2.2 automatically terminate at the expiration of 347 | the 60 day notice period specified above. 348 | 349 | (b) any software, hardware, or device, other than such Participant's 350 | Contributor Version, directly or indirectly infringes any patent, then 351 | any rights granted to You by such Participant under Sections 2.1(b) 352 | and 2.2(b) are revoked effective as of the date You first made, used, 353 | sold, distributed, or had made, Modifications made by that 354 | Participant. 355 | 356 | 8.3. If You assert a patent infringement claim against Participant 357 | alleging that such Participant's Contributor Version directly or 358 | indirectly infringes any patent where such claim is resolved (such as 359 | by license or settlement) prior to the initiation of patent 360 | infringement litigation, then the reasonable value of the licenses 361 | granted by such Participant under Sections 2.1 or 2.2 shall be taken 362 | into account in determining the amount or value of any payment or 363 | license. 364 | 365 | 8.4. In the event of termination under Sections 8.1 or 8.2 above, 366 | all end user license agreements (excluding distributors and resellers) 367 | which have been validly granted by You or any distributor hereunder 368 | prior to termination shall survive termination. 369 | 370 | 9. LIMITATION OF LIABILITY. 371 | 372 | UNDER NO CIRCUMSTANCES AND UNDER NO LEGAL THEORY, WHETHER TORT 373 | (INCLUDING NEGLIGENCE), CONTRACT, OR OTHERWISE, SHALL YOU, THE INITIAL 374 | DEVELOPER, ANY OTHER CONTRIBUTOR, OR ANY DISTRIBUTOR OF COVERED CODE, 375 | OR ANY SUPPLIER OF ANY OF SUCH PARTIES, BE LIABLE TO ANY PERSON FOR 376 | ANY INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES OF ANY 377 | CHARACTER INCLUDING, WITHOUT LIMITATION, DAMAGES FOR LOSS OF GOODWILL, 378 | WORK STOPPAGE, COMPUTER FAILURE OR MALFUNCTION, OR ANY AND ALL OTHER 379 | COMMERCIAL DAMAGES OR LOSSES, EVEN IF SUCH PARTY SHALL HAVE BEEN 380 | INFORMED OF THE POSSIBILITY OF SUCH DAMAGES. THIS LIMITATION OF 381 | LIABILITY SHALL NOT APPLY TO LIABILITY FOR DEATH OR PERSONAL INJURY 382 | RESULTING FROM SUCH PARTY'S NEGLIGENCE TO THE EXTENT APPLICABLE LAW 383 | PROHIBITS SUCH LIMITATION. SOME JURISDICTIONS DO NOT ALLOW THE 384 | EXCLUSION OR LIMITATION OF INCIDENTAL OR CONSEQUENTIAL DAMAGES, SO 385 | THIS EXCLUSION AND LIMITATION MAY NOT APPLY TO YOU. 386 | 387 | 10. U.S. GOVERNMENT END USERS. 388 | 389 | The Covered Code is a "commercial item," as that term is defined in 390 | 48 C.F.R. 2.101 (Oct. 1995), consisting of "commercial computer 391 | software" and "commercial computer software documentation," as such 392 | terms are used in 48 C.F.R. 12.212 (Sept. 1995). Consistent with 48 393 | C.F.R. 12.212 and 48 C.F.R. 227.7202-1 through 227.7202-4 (June 1995), 394 | all U.S. Government End Users acquire Covered Code with only those 395 | rights set forth herein. 396 | 397 | 11. MISCELLANEOUS. 398 | 399 | This License represents the complete agreement concerning subject 400 | matter hereof. If any provision of this License is held to be 401 | unenforceable, such provision shall be reformed only to the extent 402 | necessary to make it enforceable. This License shall be governed by 403 | California law provisions (except to the extent applicable law, if 404 | any, provides otherwise), excluding its conflict-of-law provisions. 405 | With respect to disputes in which at least one party is a citizen of, 406 | or an entity chartered or registered to do business in the United 407 | States of America, any litigation relating to this License shall be 408 | subject to the jurisdiction of the Federal Courts of the Northern 409 | District of California, with venue lying in Santa Clara County, 410 | California, with the losing party responsible for costs, including 411 | without limitation, court costs and reasonable attorneys' fees and 412 | expenses. The application of the United Nations Convention on 413 | Contracts for the International Sale of Goods is expressly excluded. 414 | Any law or regulation which provides that the language of a contract 415 | shall be construed against the drafter shall not apply to this 416 | License. 417 | 418 | 12. RESPONSIBILITY FOR CLAIMS. 419 | 420 | As between Initial Developer and the Contributors, each party is 421 | responsible for claims and damages arising, directly or indirectly, 422 | out of its utilization of rights under this License and You agree to 423 | work with Initial Developer and Contributors to distribute such 424 | responsibility on an equitable basis. Nothing herein is intended or 425 | shall be deemed to constitute any admission of liability. 426 | 427 | 13. MULTIPLE-LICENSED CODE. 428 | 429 | Initial Developer may designate portions of the Covered Code as 430 | "Multiple-Licensed". "Multiple-Licensed" means that the Initial 431 | Developer permits you to utilize portions of the Covered Code under 432 | Your choice of the NPL or the alternative licenses, if any, specified 433 | by the Initial Developer in the file described in Exhibit A. 434 | 435 | EXHIBIT A -Mozilla Public License. 436 | 437 | ``The contents of this file are subject to the Mozilla Public License 438 | Version 1.1 (the "License"); you may not use this file except in 439 | compliance with the License. You may obtain a copy of the License at 440 | http://www.mozilla.org/MPL/ 441 | 442 | Software distributed under the License is distributed on an "AS IS" 443 | basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See the 444 | License for the specific language governing rights and limitations 445 | under the License. 446 | 447 | The Original Code is ibus-avro. 448 | 449 | The Initial Developer of the Original Code is 450 | Sarim Khan 451 | 452 | Copyright (C) Sarim Khan, unless otherwise stated. All Rights Reserved. 453 | 454 | 455 | Contributor(s): Mehdi Hasan , Rifat Nabi " 456 | 457 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## YouTube Control Center (iyccenter) 2 | YouTube Control Center provides a set of useful tools for [YouTube.com](http://www.youtube.com). 3 | 4 | ### General information 5 | To compile iyccenter project you need to have these packages and libraries installed: 6 | * [python](http://www.python.org/getit/) 7 | * [nodejs](http://nodejs.org/) 8 | * [Mozilla addon-sdk](https://addons.mozilla.org/en-US/developers/builder) 9 | 10 | Folders description: 11 | * src: source code 12 | * compile: nodejs compiler 13 | * ../addon-sdk-*: latest version of [Mozilla addon-sdk](https://addons.mozilla.org/en-US/developers/builder). 14 | * preview: screenshots 15 | * template: bootstrap folder 16 | 17 | > By default, the addon-sdk folder is assumed to be one directory above the project. This can be modified using the ``--sdk`` parameter. 18 | 19 | ### How to compile this project: 20 | 1. Open a new terminal in the root dir (directory contains src, preview, template, and compile folders) 21 | 2. Run ``npm install`` to acquire the necessary nodejs packages 22 | 3. Run ``node compile/install.js`` to run iyccenter in a new Firefox profile 23 | To make the xpi run ``node compile/install.js --xpi`` 24 | For more options use ``node compile/install.js --help`` 25 | 26 | ### How to try the pre-compiled latest version: 27 | 1. Select the right branch 28 | 2. Browse the src directory 29 | 3. Download the raw *.xpi file 30 | 4. Drag and drop it into Firefox 31 | -------------------------------------------------------------------------------- /WebExtension/common.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | function cookie() { 4 | chrome.storage.local.get({ 5 | 'wide': false 6 | }, prefs => { 7 | if (prefs.wide) { 8 | chrome.cookies.set({ 9 | url: 'https://www.youtube.com', 10 | name: 'wide', 11 | value: '1' 12 | }); 13 | } 14 | else { 15 | chrome.cookies.remove({ 16 | url: 'https://www.youtube.com', 17 | name: 'wide', 18 | }); 19 | } 20 | }); 21 | } 22 | cookie(); 23 | chrome.cookies.onChanged.addListener(changeInfo => { 24 | if (changeInfo.cookie.name === 'wide' && changeInfo.cookie.value !== '1') { 25 | cookie(); 26 | } 27 | }); 28 | 29 | chrome.storage.onChanged.addListener(prefs => { 30 | if (prefs.wide) { 31 | cookie(); 32 | } 33 | if (prefs.autoplay) { 34 | chrome.cookies.set({ 35 | url: 'https://www.youtube.com', 36 | name: 'autoplay', 37 | value: String(prefs.autoplay.newValue) 38 | }); 39 | } 40 | if (prefs.autobuffer) { 41 | chrome.cookies.set({ 42 | url: 'https://www.youtube.com', 43 | name: 'autobuffer', 44 | value: String(prefs.autobuffer.newValue) 45 | }); 46 | } 47 | if (prefs.annotations) { 48 | chrome.cookies.set({ 49 | url: 'https://www.youtube.com', 50 | name: 'annotations', 51 | value: String(prefs.annotations.newValue) 52 | }); 53 | } 54 | }); 55 | 56 | // inject on startup 57 | if (chrome.app && chrome.app.getDetails) { 58 | chrome.tabs.query({ 59 | url: '*://www.youtube.com/*' 60 | }, tabs => { 61 | const contentScripts = chrome.app.getDetails().content_scripts; 62 | for (const tab of tabs) { 63 | for (const cs of contentScripts) { 64 | cs.js.forEach(file => chrome.tabs.executeScript(tab.id, { 65 | file, 66 | runAt: cs.run_at, 67 | allFrames: cs.all_frames, 68 | })); 69 | } 70 | } 71 | }); 72 | } 73 | 74 | // browserAction 75 | chrome.runtime.onMessage.addListener(request => { 76 | if (request.method === 'page-removed' || request.method === 'page-added') { 77 | window.setTimeout(() => chrome.tabs.query({ 78 | url: '*://www.youtube.com/*' 79 | }, tabs => { 80 | chrome.browserAction.setPopup({ 81 | popup: tabs.length ? '/data/popup/index.html' : '' 82 | }); 83 | }), 100); 84 | } 85 | }); 86 | chrome.browserAction.onClicked.addListener(() => { 87 | chrome.tabs.create({ 88 | url: 'https://www.youtube.com/' 89 | }); 90 | }); 91 | 92 | chrome.commands.onCommand.addListener(command => chrome.tabs.query({ 93 | url: '*://www.youtube.com/watch*' 94 | }, tabs => { 95 | const tab = tabs.filter(t => t.audible).pop() || tabs.pop(); 96 | if (tab) { 97 | chrome.tabs.sendMessage(tab.id, { 98 | method: 'command', 99 | command 100 | }); 101 | } 102 | })); 103 | 104 | // FAQs & Feedback 105 | chrome.storage.local.get({ 106 | 'version': null, 107 | 'faqs': true, 108 | 'last-update': 0, 109 | }, prefs => { 110 | const version = chrome.runtime.getManifest().version; 111 | 112 | if (prefs.version ? (prefs.faqs && prefs.version !== version) : true) { 113 | const now = Date.now(); 114 | const doUpdate = (now - prefs['last-update']) / 1000 / 60 / 60 / 24 > 30; 115 | chrome.storage.local.set({ 116 | version, 117 | 'last-update': doUpdate ? Date.now() : prefs['last-update'] 118 | }, () => { 119 | // do not display the FAQs page if last-update occurred less than 30 days ago. 120 | if (doUpdate) { 121 | const p = Boolean(prefs.version); 122 | chrome.tabs.create({ 123 | url: chrome.runtime.getManifest().homepage_url + '?version=' + version + 124 | '&type=' + (p ? ('upgrade&p=' + prefs.version) : 'install'), 125 | active: p === false 126 | }); 127 | } 128 | }); 129 | } 130 | }); 131 | 132 | { 133 | const {name, version} = chrome.runtime.getManifest(); 134 | chrome.runtime.setUninstallURL( 135 | chrome.runtime.getManifest().homepage_url + '?rd=feedback&name=' + name + '&version=' + version 136 | ); 137 | } 138 | -------------------------------------------------------------------------------- /WebExtension/data/controls.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | window.addEventListener('message', e => { 4 | if (e.data && e.data.method === 'state') { 5 | chrome.runtime.sendMessage(e.data); 6 | } 7 | }); 8 | 9 | document.documentElement.appendChild(Object.assign(document.createElement('script'), { 10 | textContent: ` 11 | var yttools = yttools || []; 12 | var iccplayer; 13 | function onYouTubePlayerReady(e) { 14 | yttools.forEach(c => { 15 | try { 16 | c(e); 17 | } 18 | catch (e) {} 19 | }); 20 | } 21 | yttools.push(e => { 22 | let state = -1; 23 | iccplayer = e; 24 | const report = (fake) => { 25 | window.postMessage({ 26 | method: 'state', 27 | state, 28 | fake, 29 | time: e.getProgressState(), 30 | data: e.getVideoData() 31 | }, '*'); 32 | }; 33 | report(true); 34 | e.addEventListener('onStateChange', s => { 35 | state = s; 36 | report(); 37 | }); 38 | 39 | // controls 40 | window.addEventListener('message', ({data}) => { 41 | if (data) { 42 | if (data.method === 'get-state') { 43 | report(true); 44 | } 45 | else if (data.method === 'command') { 46 | if (data.command === 'stop') { 47 | e.stopVideo(); 48 | } 49 | else if (data.command === 'pause') { 50 | e.playVideo(); 51 | } 52 | else if (data.command === 'play') { 53 | e.pauseVideo(); 54 | } 55 | else if (data.command === 'previous') { 56 | e.previousVideo(); 57 | } 58 | else if (data.command === 'next') { 59 | e.nextVideo(); 60 | } 61 | else if (data.command === 'seek-to') { 62 | e.seekTo(parseInt(data.percent * e.getDuration())); 63 | } 64 | } 65 | } 66 | }); 67 | }); 68 | 69 | { 70 | const observe = (object, property, callback) => { 71 | let value; 72 | const descriptor = Object.getOwnPropertyDescriptor(object, property); 73 | Object.defineProperty(object, property, { 74 | enumerable: true, 75 | configurable: true, 76 | get: () => value, 77 | set: v => { 78 | callback(v); 79 | if (descriptor && descriptor.set) { 80 | descriptor.set(v); 81 | } 82 | value = v; 83 | return value; 84 | } 85 | }); 86 | }; 87 | observe(window, 'ytplayer', ytplayer => { 88 | observe(ytplayer, 'config', config => { 89 | if (config && config.args) { 90 | config.args.jsapicallback = 'onYouTubePlayerReady'; 91 | } 92 | }); 93 | }); 94 | } 95 | ` 96 | })); 97 | 98 | chrome.runtime.onMessage.addListener(request => { 99 | if (request.method === 'get-state' || request.method === 'command') { 100 | window.postMessage(request, '*'); 101 | } 102 | }); 103 | -------------------------------------------------------------------------------- /WebExtension/data/css.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | { 4 | const style = document.createElement('style'); 5 | style.type = 'text/css'; 6 | style.textContent = ''; 7 | document.documentElement.appendChild(style); 8 | 9 | // reinsert when body is ready 10 | const mutation = new MutationObserver(() => { 11 | if (document.body) { 12 | document.documentElement.appendChild(style); 13 | mutation.disconnect(); 14 | } 15 | }); 16 | mutation.observe(document, {childList: true, subtree: true}); 17 | 18 | const queries = { 19 | searchbar: ['#yt-masthead-container', '#container.ytd-masthead'], 20 | sidebar: ['#watch7-sidebar-contents', '#related.ytd-watch'], 21 | comments: ['#watch-discussion', 'ytd-comments'], 22 | info: ['#watch7-user-header', '#watch8-action-buttons', '#info.ytd-video-primary-info-renderer'], 23 | details: ['#action-panel-details', '#meta.ytd-watch'] 24 | }; 25 | let prefs = { 26 | searchbar: true, 27 | sidebar: true, 28 | comments: true, 29 | info: true, 30 | details: true 31 | }; 32 | 33 | function update() { 34 | style.textContent = Object.entries(prefs).map(([key, value]) => { 35 | if (value) { 36 | return ''; 37 | } 38 | else { 39 | return queries[key].join(', ') + ' {display: none;}'; 40 | } 41 | }).join('\n'); 42 | } 43 | 44 | chrome.storage.local.get(prefs, ps => { 45 | prefs = ps; 46 | update(); 47 | }); 48 | chrome.storage.onChanged.addListener(ps => { 49 | const a = Object.keys(ps).filter(n => n in prefs); 50 | if (a.length) { 51 | a.forEach(key => prefs[key] = ps[key].newValue); 52 | update(); 53 | } 54 | }); 55 | } 56 | -------------------------------------------------------------------------------- /WebExtension/data/icons/16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/inbasic/iyccenter/aed528cdf04d858751c7f150911a4c141b649392/WebExtension/data/icons/16.png -------------------------------------------------------------------------------- /WebExtension/data/icons/32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/inbasic/iyccenter/aed528cdf04d858751c7f150911a4c141b649392/WebExtension/data/icons/32.png -------------------------------------------------------------------------------- /WebExtension/data/icons/48.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/inbasic/iyccenter/aed528cdf04d858751c7f150911a4c141b649392/WebExtension/data/icons/48.png -------------------------------------------------------------------------------- /WebExtension/data/icons/64.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/inbasic/iyccenter/aed528cdf04d858751c7f150911a4c141b649392/WebExtension/data/icons/64.png -------------------------------------------------------------------------------- /WebExtension/data/next_track.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | { 3 | const next = checked => { 4 | const e = document.getElementById('autoplay-checkbox'); 5 | if (e) { 6 | e.checked = checked; 7 | } 8 | const t = document.querySelector('paper-toggle-button#toggle'); 9 | if (t) { 10 | if (t.getAttribute('aria-pressed') !== String(checked)) { 11 | t.click(); 12 | } 13 | } 14 | }; 15 | window.addEventListener('message', function _(e) { 16 | if (e.data && e.data.method === 'state' && e.data.state === 1) { 17 | window.removeEventListener('message', _); 18 | chrome.storage.local.get({ 19 | next: true 20 | }, prefs => next(prefs.next)); 21 | } 22 | }); 23 | chrome.storage.onChanged.addListener(prefs => { 24 | if (prefs.next) { 25 | next(prefs.next.newValue); 26 | } 27 | }); 28 | } 29 | -------------------------------------------------------------------------------- /WebExtension/data/no_buffer.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | // annotation and no buffer 4 | document.documentElement.appendChild(Object.assign(document.createElement('script'), { 5 | textContent: `{ 6 | // stop the new layer from starting the video 7 | if (document.cookie.includes('autoplay=false')) { 8 | 9 | var yttools = yttools || []; 10 | yttools.push(e => { 11 | // Method 0 12 | e.stopVideo(); 13 | // Method 1; prevent polymer from starting video 14 | const playVideo = e.playVideo; 15 | e.playVideo = function() { 16 | const err = new Error().stack; 17 | if (err && err.indexOf('onPlayerReady_') !== -1) { 18 | return e.stopVideo(); 19 | } 20 | playVideo.apply(this, arguments); 21 | }; 22 | // Method 2; stop subsequent plays 23 | document.addEventListener('yt-page-data-fetched', () => e.stopVideo && e.stopVideo()); 24 | }); 25 | } 26 | const observe = (object, property, callback) => { 27 | let value; 28 | const descriptor = Object.getOwnPropertyDescriptor(object, property); 29 | Object.defineProperty(object, property, { 30 | enumerable: true, 31 | configurable: true, 32 | get: () => value, 33 | set: v => { 34 | callback(v); 35 | if (descriptor && descriptor.set) { 36 | descriptor.set(v); 37 | } 38 | value = v; 39 | return value; 40 | } 41 | }); 42 | }; 43 | observe(window, 'ytplayer', ytplayer => { 44 | observe(ytplayer, 'config', config => { 45 | if (config && config.args) { 46 | if (document.cookie.includes('autoplay=false')) { 47 | Object.defineProperty(config.args, 'autoplay', { 48 | configurable: true, 49 | get: () => '0' 50 | }); 51 | config.args.fflags = config.args.fflags.replace('legacy_autoplay_flag=true', 'legacy_autoplay_flag=false'); 52 | } 53 | if (document.cookie.includes('annotations=false')) { 54 | Object.defineProperty(config.args, 'iv_load_policy', { 55 | configurable: true, 56 | get: () => '3' 57 | }); 58 | } 59 | if (document.cookie.includes('autobuffer=true')) { 60 | delete config.args.ad3_module; 61 | } 62 | } 63 | }); 64 | }); 65 | }` 66 | })); 67 | // autbuffer 68 | document.documentElement.appendChild(Object.assign(document.createElement('script'), { 69 | textContent: ` 70 | yttools.push(e => { 71 | let proceed = true; 72 | if (document.cookie.includes('autobuffer=true')) { 73 | e.addEventListener('onStateChange', () => { 74 | if (proceed) { 75 | proceed = false; 76 | e.pauseVideo(); 77 | } 78 | }); 79 | e.pauseVideo(); 80 | } 81 | }); 82 | `})); 83 | -------------------------------------------------------------------------------- /WebExtension/data/page.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | { 3 | const callback = () => chrome.runtime.sendMessage({ 4 | method: 'page-added' 5 | }); 6 | document.addEventListener('spfrequest', callback); 7 | window.addEventListener('yt-navigate-start', callback); 8 | callback(); 9 | } 10 | 11 | window.addEventListener('unload', () => { 12 | try { 13 | chrome.runtime.sendMessage({ 14 | method: 'page-removed' 15 | }); 16 | } 17 | catch (e) {} 18 | }); 19 | -------------------------------------------------------------------------------- /WebExtension/data/popup/history/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 73 |
History
74 |
75 | 76 | 77 | 78 | 79 | 80 | 81 |
82 |
83 | 86 | 87 | 88 | 89 | -------------------------------------------------------------------------------- /WebExtension/data/popup/history/index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | document.addEventListener('click', e => { 4 | const target = e.target; 5 | if (target.href) { 6 | e.preventDefault(); 7 | chrome.tabs.query({ 8 | active: true, 9 | currentWindow: true, 10 | url: ['*://*.youtube.com/*'] 11 | }, ([tab]) => { 12 | const url = target.href; 13 | chrome.tabs[tab ? 'update' : 'create']({ 14 | url 15 | }, () => { 16 | if (url.endsWith('/feed/history')) { 17 | window.top.close(); 18 | } 19 | }); 20 | }); 21 | } 22 | }); 23 | 24 | var req = new XMLHttpRequest(); 25 | req.open('GET', 'https://www.youtube.com/feed/history'); 26 | req.onload = () => { 27 | 28 | const tbody = document.querySelector('tbody'); 29 | try { 30 | let ytInitialData = /window\["ytInitialData"\] = (\{.*\})/.exec(req.responseText); 31 | ytInitialData = JSON.parse(ytInitialData[1]); 32 | const videos = ytInitialData.contents.twoColumnBrowseResultsRenderer 33 | .tabs['0'].tabRenderer.content.sectionListRenderer 34 | .contents['0'].itemSectionRenderer.contents 35 | .map(o => Object.assign({ 36 | title: {}, 37 | descriptionSnippet: {}, 38 | lengthText: {}, 39 | }, o.videoRenderer)); 40 | 41 | if (videos.length === 1 && !videos[0].title.simpleText) { 42 | const tr = document.createElement('tr'); 43 | const td = document.createElement('td'); 44 | td.setAttribute('colspan', 2); 45 | td.textContent = 'Are you logged in?'; 46 | tr.appendChild(td); 47 | tbody.appendChild(tr); 48 | } 49 | else { 50 | videos.map(o => ({ 51 | title: o.title.simpleText, 52 | description: o.descriptionSnippet.simpleText, 53 | length: o.lengthText.simpleText, 54 | videoId: o.videoId 55 | })).forEach(o => { 56 | const tr = document.createElement('tr'); 57 | tr.title = (o.title + '\n\n' + o.description).trim(); 58 | const time = document.createElement('td'); 59 | time.textContent = (o.length || '--:--'); 60 | const title = document.createElement('td'); 61 | const a = document.createElement('a'); 62 | a.href = 'https://www.youtube.com/watch?v=' + o.videoId; 63 | a.textContent = o.title; 64 | title.appendChild(a); 65 | tr.appendChild(time); 66 | tr.appendChild(title); 67 | tbody.appendChild(tr); 68 | }); 69 | } 70 | } 71 | catch (e) { 72 | const tr = document.createElement('tr'); 73 | const td = document.createElement('td'); 74 | td.setAttribute('colspan', 2); 75 | td.textContent = e.message; 76 | tr.appendChild(td); 77 | tbody.appendChild(tr); 78 | } 79 | document.body.dataset.loading = false; 80 | }; 81 | req.send(); 82 | -------------------------------------------------------------------------------- /WebExtension/data/popup/history/loading.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/inbasic/iyccenter/aed528cdf04d858751c7f150911a4c141b649392/WebExtension/data/popup/history/loading.gif -------------------------------------------------------------------------------- /WebExtension/data/popup/icons/back.svg: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /WebExtension/data/popup/icons/backward.svg: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /WebExtension/data/popup/icons/checked.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/inbasic/iyccenter/aed528cdf04d858751c7f150911a4c141b649392/WebExtension/data/popup/icons/checked.png -------------------------------------------------------------------------------- /WebExtension/data/popup/icons/cover.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/inbasic/iyccenter/aed528cdf04d858751c7f150911a4c141b649392/WebExtension/data/popup/icons/cover.jpg -------------------------------------------------------------------------------- /WebExtension/data/popup/icons/for.svg: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /WebExtension/data/popup/icons/forward.svg: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /WebExtension/data/popup/icons/history.svg: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /WebExtension/data/popup/icons/pause.svg: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /WebExtension/data/popup/icons/play.svg: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /WebExtension/data/popup/icons/settings.svg: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /WebExtension/data/popup/icons/stop.svg: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /WebExtension/data/popup/icons/uchecked.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/inbasic/iyccenter/aed528cdf04d858751c7f150911a4c141b649392/WebExtension/data/popup/icons/uchecked.png -------------------------------------------------------------------------------- /WebExtension/data/popup/index.css: -------------------------------------------------------------------------------- 1 | body { 2 | margin: 0; 3 | font: 400 13px/24px Roboto,sans-serif; 4 | background-color: #fff; 5 | overflow: hidden; 6 | width: 400px; 7 | transition: all 0.3s ease-out; 8 | } 9 | h3 { 10 | font-weight: 400; 11 | font-size: 140%; 12 | padding: 10px 20px; 13 | margin: 0; 14 | } 15 | body>div { 16 | min-width: 400px; 17 | overflow: hidden; 18 | } 19 | header { 20 | height: 46px; 21 | background-color: #4d8eee; 22 | color: #fff; 23 | font-size: 120%; 24 | } 25 | input[type=button] { 26 | outline: none; 27 | border: solid 1px #c5c5c5; 28 | background-color: #fff; 29 | min-width: 80px; 30 | cursor: pointer; 31 | } 32 | input[type=button]:active { 33 | opacity: 0.5; 34 | } 35 | 36 | label { 37 | display: flex; 38 | align-items: center; 39 | } 40 | label[type=checkbox]>span { 41 | display: inline-block; 42 | background-image: url('icons/uchecked.png'); 43 | background-repeat: no-repeat; 44 | background-position: left center; 45 | height: 32px; 46 | width: 52px; 47 | line-height: 32px; 48 | cursor: pointer; 49 | } 50 | input:checked ~ span { 51 | background-image: url('icons/checked.png'); 52 | } 53 | label>input[type=checkbox] { 54 | display: none; 55 | } 56 | 57 | summary { 58 | outline: none; 59 | cursor: pointer; 60 | user-select: none; 61 | padding: 5px; 62 | background: rgba(0,0,0,0.05); 63 | } 64 | 65 | select { 66 | outline: none; 67 | } 68 | 69 | #cover { 70 | background-repeat: no-repeat; 71 | background-size: cover; 72 | min-height: 301px; 73 | position: relative; 74 | } 75 | 76 | #move-to-settings, 77 | #move-to-history { 78 | position: absolute; 79 | top: 15px; 80 | width: 32px; 81 | height: 32px; 82 | cursor: pointer; 83 | border-radius: 50%; 84 | background-color: rgba(37, 37, 37, 0.25); 85 | background-position: center center; 86 | background-repeat: no-repeat; 87 | } 88 | #move-to-settings { 89 | left: 15px; 90 | background-image: url(icons/settings.svg); 91 | background-size: 32px; 92 | } 93 | #move-to-history { 94 | right: 15px; 95 | background-image: url(icons/history.svg); 96 | background-size: 26px; 97 | } 98 | 99 | #backward, 100 | #forward, 101 | #stop, 102 | #play { 103 | cursor: pointer; 104 | background-size: 16px; 105 | background-repeat: no-repeat; 106 | width: 32px; 107 | height: 32px; 108 | display: inline-block; 109 | border-radius: 50%; 110 | transition: opacity 0.5s ease; 111 | opacity: 1; 112 | } 113 | #backward:active, 114 | #forward:active, 115 | #stop:active, 116 | #play:active { 117 | opacity: 0.1; 118 | } 119 | #play { 120 | background-image: url(icons/pause.svg); 121 | background-position: center center; 122 | background-size: 15px; 123 | width: 52px; 124 | height: 52px; 125 | margin: 0 30px; 126 | box-shadow: 0 0 16px 1px rgba(0, 148, 255, 0.37); 127 | background-color: transparent; 128 | } 129 | #play[data-state=pause] { 130 | background-image: url(icons/play.svg); 131 | background-position: left 19px center; 132 | background-size: 20px; 133 | } 134 | #backward { 135 | background-image: url(icons/backward.svg); 136 | background-position: left 6px center; 137 | } 138 | #forward { 139 | background-image: url(icons/forward.svg); 140 | background-position: right 6px center; 141 | } 142 | #stop { 143 | background-image: url(icons/stop.svg); 144 | background-position: center center; 145 | margin-left: 5px; 146 | } 147 | 148 | #progress { 149 | background-color: #c1c1c1; 150 | border-radius: 2px; 151 | cursor: pointer; 152 | margin: 0 20px; 153 | } 154 | #progress div { 155 | height: 4px; 156 | background-color: #4d8eee; 157 | width: 0%; 158 | pointer-events: none; 159 | border-radius: 2px; 160 | } 161 | 162 | #tools { 163 | padding: 5px; 164 | position: absolute; 165 | right: 0; 166 | top: 0; 167 | } 168 | 169 | #title { 170 | padding: 20px 10px; 171 | font-size: 130%; 172 | overflow: hidden; 173 | text-overflow: ellipsis; 174 | white-space: nowrap; 175 | } 176 | #history, 177 | #settings { 178 | position: relative; 179 | } 180 | #back, 181 | #for { 182 | position: absolute; 183 | top: 8px; 184 | background-size: 32px; 185 | width: 32px; 186 | height: 32px; 187 | cursor: pointer; 188 | } 189 | #back { 190 | right: 14px; 191 | background: url(icons/back.svg) center center no-repeat; 192 | } 193 | #for { 194 | left: 14px; 195 | background: url(icons/for.svg) center center no-repeat; 196 | } 197 | 198 | [hbox] { 199 | display: flex; 200 | } 201 | [vbox] { 202 | display: flex; 203 | flex-direction: column; 204 | } 205 | [pack=center] { 206 | justify-content: center; 207 | } 208 | [pack=end] { 209 | justify-content: flex-end; 210 | } 211 | [align=center] { 212 | align-items: center; 213 | } 214 | [align=end] { 215 | align-items: flex-end; 216 | } 217 | [flex="1"] { 218 | flex: 1; 219 | } 220 | 221 | #extension, 222 | #player, 223 | #page { 224 | padding-left: 15px; 225 | } 226 | #extension, 227 | #page { 228 | padding-bottom: 15px; 229 | } 230 | 231 | body[data-player=false] #cover::before { 232 | display: flex; 233 | align-items: center; 234 | justify-content: center; 235 | content: 'Please start a YouTube video on a browser tab for the controls to appear'; 236 | margin: 40px 20px; 237 | line-height: 16px; 238 | } 239 | body[data-player=false] #controls, 240 | body[data-player=false] #info { 241 | opacity: 0.2; 242 | pointer-events: none; 243 | } 244 | 245 | iframe { 246 | width: 100%; 247 | height: 100%; 248 | border: none; 249 | background-color: #fff; 250 | } 251 | 252 | body[data-view=controls] { 253 | height: 500px; 254 | transform: translateX(-400px); 255 | } 256 | body[data-view=settings] { 257 | transform: translateX(0); 258 | } 259 | 260 | body[data-view=history] { 261 | transform: translateX(-800px); 262 | } 263 | -------------------------------------------------------------------------------- /WebExtension/data/popup/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 |
9 |
Settings
10 |
11 |

Player

12 |
13 | 18 | 23 | 28 | 33 | 48 | 53 |
54 |

Page

55 |
56 | 61 | 66 | 71 | 76 | 81 |
82 |

Extension

83 |
84 | 89 |
90 |
91 | 92 |
93 |
94 |
95 |
96 |
97 |
98 |
99 | 100 |
101 |
102 |
103 |
104 |
105 |
106 | 107 | 108 | 109 | 110 |
111 |
112 |
113 |
114 | 115 |
116 | 117 | 118 | 119 | 120 | -------------------------------------------------------------------------------- /WebExtension/data/popup/index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var tabId; 4 | 5 | var iframe = { 6 | e: document.querySelector('iframe'), 7 | show: path => { 8 | iframe.e.src = path; 9 | }, 10 | hide: () => iframe.e.src = '' 11 | }; 12 | document.addEventListener('click', () => iframe.hide()); 13 | 14 | document.getElementById('progress').addEventListener('click', ({offsetX, target}) => { 15 | if (tabId) { 16 | chrome.tabs.sendMessage(tabId, { 17 | method: 'command', 18 | command: 'seek-to', 19 | percent: offsetX / target.clientWidth 20 | }); 21 | } 22 | }); 23 | 24 | const state = () => { 25 | //console.log('here'); 26 | if (tabId) { 27 | chrome.tabs.sendMessage(tabId, { 28 | method: 'get-state' 29 | }); 30 | } 31 | else { 32 | chrome.tabs.query({ 33 | url: '*://www.youtube.com/watch*' 34 | }, tabs => { 35 | const tab = tabs.filter(t => t.audible).pop() || tabs.pop(); 36 | if (tab) { 37 | chrome.tabs.sendMessage(tab.id, { 38 | method: 'get-state' 39 | }); 40 | } 41 | document.body.dataset.player = Boolean(tab); 42 | }); 43 | } 44 | }; 45 | window.setInterval(state, 1000); 46 | 47 | var id = null; 48 | 49 | chrome.runtime.onMessage.addListener((request, sender) => { 50 | if (request.method === 'state') { 51 | const title = (request.data ? request.data.title : '') || sender.tab.title.replace(' - YouTube', ''); 52 | if (request.data && request.data.video_id !== id) { 53 | id = request.data.video_id; 54 | if (localStorage.getItem('thumbnail') !== 'false') { 55 | window.setTimeout(() => { 56 | document.getElementById('cover').style['background-image'] = 57 | `url(https://img.youtube.com/vi/${id}/hqdefault.jpg)`; 58 | }); 59 | } 60 | else { 61 | document.getElementById('cover').style['background-image'] = 'url(icons/cover.jpg)'; 62 | } 63 | } 64 | 65 | tabId = sender.tab.id; 66 | document.getElementById('title').textContent = (title || 'video title is not available yet!'); 67 | document.querySelector('#progress div').style.width = request.time.current / request.time.duration * 100 + '%'; 68 | document.getElementById('play').dataset.state = request.state === 1 ? 'play' : 'pause'; 69 | } 70 | }); 71 | document.addEventListener('DOMContentLoaded', state); 72 | 73 | document.addEventListener('click', ({target}) => { 74 | const cmd = target.dataset.cmd; 75 | if (cmd === 'back') { 76 | document.body.dataset.view = 'controls'; 77 | } 78 | else if (cmd === 'move-to-settings') { 79 | document.body.dataset.view = 'settings'; 80 | } 81 | else if (cmd === 'move-to-history') { 82 | document.body.dataset.view = 'history'; 83 | iframe.show('history/index.html'); 84 | } 85 | else if (cmd && tabId) { 86 | chrome.tabs.sendMessage(tabId, { 87 | method: 'command', 88 | command: cmd === 'play' ? target.dataset.state : cmd 89 | }); 90 | } 91 | }); 92 | 93 | document.addEventListener('change', ({target}) => { 94 | if (target.name) { 95 | chrome.storage.local.set({ 96 | [target.name]: target.checked 97 | }); 98 | if (target.name === 'thumbnail') { 99 | localStorage.setItem('thumbnail', target.checked); 100 | } 101 | } 102 | else if (target.id) { 103 | chrome.storage.local.set({ 104 | [target.id]: target.value 105 | }); 106 | } 107 | }); 108 | 109 | document.getElementById('page').addEventListener('toggle', e => { 110 | localStorage.setItem('page.toggle', e.target.open); 111 | }); 112 | document.getElementById('player').addEventListener('toggle', e => { 113 | localStorage.setItem('player.toggle', e.target.open); 114 | }); 115 | try { 116 | document.getElementById('page').open = localStorage.getItem('page.toggle') === 'true'; 117 | document.getElementById('player').open = localStorage.getItem('player.toggle') === 'true'; 118 | 119 | if (localStorage.getItem('player.toggle') === null) { 120 | document.getElementById('player').open = true; 121 | } 122 | } 123 | catch (e) {} 124 | 125 | chrome.storage.local.get({ 126 | // player 127 | wide: false, 128 | next: true, 129 | autoplay: true, 130 | autobuffer: false, 131 | annotations: true, 132 | quality: 'default', 133 | // page 134 | searchbar: true, 135 | sidebar: true, 136 | comments: true, 137 | info: true, 138 | details: true, 139 | // extension 140 | thumbnail: true 141 | }, prefs => { 142 | Object.entries(prefs).forEach(([key, value]) => { 143 | if (typeof value === 'string') { 144 | document.getElementById(key).value = value; 145 | } 146 | else { 147 | document.querySelector(`[name="${key}"]`).checked = value; 148 | } 149 | }); 150 | }); 151 | -------------------------------------------------------------------------------- /WebExtension/data/quality.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | document.documentElement.appendChild(Object.assign(document.createElement('script'), { 4 | textContent: `{ 5 | let quality = 'default'; 6 | const setQuality = () => { 7 | if (quality === 'default') { 8 | return; 9 | } 10 | if (iccplayer && iccplayer.setPlaybackQuality) { 11 | if (quality === 'highest') { 12 | quality = iccplayer.getAvailableQualityLevels()[0]; 13 | } 14 | console.log('quality is set to', quality); 15 | iccplayer.setPlaybackQuality(quality); 16 | } 17 | } 18 | window.addEventListener('message', ({data}) => { 19 | if (data && data.method === 'command' && data.command === 'quality') { 20 | quality = data.value; 21 | setQuality(); 22 | } 23 | if (data && data.method === 'state' && data.state === 1 && data.fake !== true) { 24 | setQuality(); 25 | } 26 | }); 27 | }` 28 | })); 29 | { 30 | const quality = value => window.postMessage({ 31 | method: 'command', 32 | command: 'quality', 33 | value 34 | }, '*'); 35 | chrome.storage.local.get({ 36 | quality: 'default' 37 | }, prefs => quality(prefs.quality)); 38 | chrome.storage.onChanged.addListener(prefs => { 39 | if (prefs.quality) { 40 | quality(prefs.quality.newValue); 41 | } 42 | }); 43 | } 44 | -------------------------------------------------------------------------------- /WebExtension/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "YouTube Control Center", 3 | "short_name": "iycc", 4 | "description": "YouTube popup pause and resume as well as no buffer, HD quality, wide screen, ...", 5 | "author": "InBasic", 6 | "version": "0.6.3", 7 | "manifest_version": 2, 8 | "permissions": [ 9 | "storage", 10 | "tabs", 11 | "cookies", 12 | "*://*.youtube.com/*" 13 | ], 14 | "background": { 15 | "persistent": false, 16 | "scripts": [ 17 | "common.js" 18 | ] 19 | }, 20 | "content_scripts": [{ 21 | "matches": [ 22 | "*://www.youtube.com/*" 23 | ], 24 | "js": [ 25 | "data/page.js", 26 | "data/controls.js", 27 | "data/no_buffer.js", 28 | "data/quality.js", 29 | "data/next_track.js", 30 | "data/css.js" 31 | ], 32 | "run_at": "document_start", 33 | "all_frames": false 34 | }], 35 | "icons": { 36 | "16": "data/icons/16.png", 37 | "32": "data/icons/32.png", 38 | "48": "data/icons/48.png", 39 | "64": "data/icons/64.png" 40 | }, 41 | "browser_action": { 42 | "default_icon": { 43 | "16": "data/icons/16.png", 44 | "32": "data/icons/32.png", 45 | "48": "data/icons/48.png", 46 | "64": "data/icons/64.png" 47 | }, 48 | "default_title": "YouTube Control Center\n\n-> Opens YouTube when no player is detected.\n-> Displays the control popup when there is at least one YouTube player" 49 | }, 50 | "homepage_url": "http://add0n.com/control-center.html", 51 | 52 | "commands": { 53 | "pause": { 54 | "description": "Stat the player" 55 | }, 56 | "stop": { 57 | "description": "Stop the player" 58 | }, 59 | "play": { 60 | "description": "Pause the player" 61 | }, 62 | "next": { 63 | "description": "Move to the next track" 64 | }, 65 | "previous": { 66 | "description": "Move to the previous track" 67 | } 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /XUL/builds/0.3.6.xpi: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/inbasic/iyccenter/aed528cdf04d858751c7f150911a4c141b649392/XUL/builds/0.3.6.xpi -------------------------------------------------------------------------------- /XUL/builds/0.3.6b1.xpi: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/inbasic/iyccenter/aed528cdf04d858751c7f150911a4c141b649392/XUL/builds/0.3.6b1.xpi -------------------------------------------------------------------------------- /XUL/builds/0.4.0.xpi: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/inbasic/iyccenter/aed528cdf04d858751c7f150911a4c141b649392/XUL/builds/0.4.0.xpi -------------------------------------------------------------------------------- /XUL/builds/0.4.1.xpi: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/inbasic/iyccenter/aed528cdf04d858751c7f150911a4c141b649392/XUL/builds/0.4.1.xpi -------------------------------------------------------------------------------- /XUL/src/.jpmignore: -------------------------------------------------------------------------------- 1 | .jpmignore 2 | builds/* 3 | .git 4 | *.xpi 5 | .DS_Store 6 | -------------------------------------------------------------------------------- /XUL/src/data/end.js: -------------------------------------------------------------------------------- 1 | /* globals XPCNativeWrapper, exportFunction, unsafeWindow, self */ 2 | 'use strict'; 3 | 4 | var player; 5 | var prefs; 6 | // styling 7 | self.port.on('prefs', (prefs) => { 8 | let css = '' + 9 | (prefs.sidebar ? '' : '#watch7-sidebar {display: none;}') + 10 | (prefs.discussion ? '' : '#watch-discussion {display: none;}') + 11 | (prefs.views ? '' : '#watch7-views-info {display: none;}') + 12 | (prefs.actions ? '' : '#watch8-secondary-actions {display: none;}') + 13 | (!prefs.views && !prefs.actions ? '#watch8-action-buttons {display: none;}' : '') + 14 | (prefs.details ? '' : '#action-panel-details {display: none;}') + 15 | (prefs.header ? '' : '#masthead-positioner-height-offset, #masthead-positioner {display: none;}') + 16 | (prefs.playlist ? '' : '.autoplay-bar {opacity: 0.3;}.checkbox-on-off {display: none;}'); 17 | if (css) { 18 | try { 19 | let elmHead = document.getElementsByTagName('head')[0]; 20 | let elmStyle = document.createElement('style'); 21 | elmStyle.type = 'text/css'; 22 | elmHead.appendChild(elmStyle); 23 | elmStyle.textContent = css; 24 | } 25 | catch (e) { 26 | if (!document.styleSheets.length) { 27 | document.createStyleSheet(); 28 | } 29 | document.styleSheets[0].cssText += css; 30 | } 31 | } 32 | }); 33 | // 34 | function iyccListenerChange (e) { 35 | try { 36 | let id = unsafeWindow.ytplayer.config.args.video_id; 37 | if (e === 1 || e === 3 || e === 5) { 38 | self.port.emit('info', { 39 | duration: player.getDuration(), 40 | title: unsafeWindow.ytplayer.config.args.title, 41 | id 42 | }); 43 | } 44 | self.port.emit('onStateChange', id, e); 45 | // quality 46 | if (e === 1) { 47 | let levels = player.getAvailableQualityLevels(); 48 | if (levels.length) { 49 | let val = ['tiny', 'small', 'medium', 'large', 'hd720', 'hd1080', 'hd1440', 'hd2160', 'highres', 'default'][+prefs.quality]; 50 | if (val === 'highres') { 51 | val = levels[0]; 52 | } 53 | player.setPlaybackQuality(levels.indexOf(val) !== -1 ? val : 'default'); 54 | } 55 | } 56 | } 57 | catch (e) {} 58 | } 59 | exportFunction(iyccListenerChange, unsafeWindow, { 60 | defineAs: 'iyccListenerChange' 61 | }); 62 | // other extensions/greasemonkey scripts that listen over the global "onYouTubePlayerReady" function should 63 | // push their listener into the global yttools object. This way all the listeners are called without interfering 64 | unsafeWindow.yttools = unsafeWindow.yttools || []; 65 | unsafeWindow.yttools.push(function (e) { 66 | e = XPCNativeWrapper.unwrap(e); 67 | player = e; 68 | let pathname = document.location.pathname; 69 | if (prefs.autoplay === false && pathname.startsWith('/user') || pathname.startsWith('/channel')) { 70 | try { 71 | e.stopVideo(); 72 | } 73 | catch(e) {} 74 | } 75 | // change volume 76 | try { 77 | e.setVolume(+prefs.volume); 78 | } 79 | catch(e) {} 80 | // auto buffer 81 | if (prefs.autobuffer && !prefs.autoplay) { 82 | try { 83 | e.playVideo(); 84 | e.pauseVideo(); 85 | } 86 | catch (e) {} 87 | } 88 | // state changes 89 | try { 90 | e.addEventListener('onStateChange', 'iyccListenerChange'); 91 | iyccListenerChange(e.getPlayerState()); 92 | } 93 | catch (e) {} 94 | // listeners 95 | self.port.on('play', () => e.playVideo()); 96 | self.port.on('pause', () => e.pauseVideo()); 97 | self.port.on('stop', () => { 98 | if (e.seekTo) { 99 | e.seekTo(0); 100 | } 101 | e.stopVideo(); 102 | e.clearVideo(); 103 | }); 104 | self.port.on('volume', (v) => e.setVolume(v)); 105 | self.port.on('skip', function () { 106 | var div = document.querySelector('.playlist-behavior-controls'); 107 | if (!div) { 108 | return; 109 | } 110 | var next = div.querySelector('.next-playlist-list-item'); 111 | if (next) { 112 | next.click(); 113 | } 114 | }); 115 | // details 116 | if (prefs.moreDetails) { 117 | let button = document.querySelector('#action-panel-details button'); 118 | if (button) { 119 | window.setTimeout(() => button.click(), 2000); 120 | } 121 | } 122 | }); 123 | function onYouTubePlayerReady (e) { 124 | (unsafeWindow.yttools || []).forEach(c => c(e)); 125 | } 126 | try { 127 | exportFunction(onYouTubePlayerReady, unsafeWindow, { 128 | defineAs: 'onYouTubePlayerReady' 129 | }); 130 | } 131 | catch (e) {} 132 | 133 | // config.args 134 | self.port.on('prefs', (p) => { 135 | prefs = p; 136 | 137 | let script = document.createElement('script'); 138 | script.textContent = ` 139 | (function (observe) { 140 | observe(window, 'ytplayer', (ytplayer) => { 141 | observe(ytplayer, 'config', (config) => { 142 | if (config && config.args) { 143 | if (${prefs.skipads}) { 144 | delete config.args.ad3_module; 145 | } 146 | if (${prefs.theme} === 1) { 147 | config.args.theme = 'light'; 148 | } 149 | if (${prefs.rel} === false) { 150 | config.args.rel = '0'; 151 | } 152 | if (${prefs.autohide}) { 153 | config.args.autohide = '1'; 154 | } 155 | if (${prefs.autofshow}) { 156 | config.args.autohide = '0'; 157 | } 158 | if (${prefs.autoplay} === false && document.location.href.indexOf('autoplay=1') === -1) { 159 | Object.defineProperty(config.args, 'autoplay', { 160 | configurable: true, 161 | get: () => '0' 162 | }); 163 | config.args.fflags = config.args.fflags.replace("legacy_autoplay_flag=true", "legacy_autoplay_flag=false"); 164 | } 165 | if (${prefs.annotations} === false) { 166 | Object.defineProperty(config.args, 'iv_load_policy', { 167 | configurable: true, 168 | get: () => '3' 169 | }); 170 | } 171 | if (${prefs.disablekb}) { 172 | config.args.autohide = '1'; 173 | } 174 | if (${prefs.disablekb} === false) { 175 | config.args.autohide = '0'; 176 | } 177 | if (${prefs.color} === 1) { 178 | config.args.color = 'white'; 179 | } 180 | config.args.jsapicallback = 'onYouTubePlayerReady'; 181 | } 182 | }); 183 | }); 184 | })(function (object, property, callback) { 185 | let value; 186 | let descriptor = Object.getOwnPropertyDescriptor(object, property); 187 | Object.defineProperty(object, property, { 188 | enumerable: true, 189 | configurable: true, 190 | get: () => value, 191 | set: (v) => { 192 | callback(v); 193 | if (descriptor && descriptor.set) { 194 | descriptor.set(v); 195 | } 196 | value = v; 197 | return value; 198 | } 199 | }); 200 | }); 201 | // HTML5 spf forward 202 | document.addEventListener('spfpartprocess', function (e) { 203 | if (e.detail && e.detail.part && e.detail.part.data && e.detail.part.data.swfcfg) { 204 | if (${prefs.skipads}) { 205 | delete e.detail.part.data.swfcfg.args.ad3_module; 206 | } 207 | let loc = document.location.href; 208 | if (loc.indexOf('&list=') !== -1 && loc.indexOf('&index=') !== -1) { 209 | return; 210 | } 211 | if (${prefs.autoplay} === false && loc.indexOf('autoplay=1') === -1) { 212 | e.detail.part.data.swfcfg.args.autoplay = '0'; 213 | } 214 | } 215 | }); 216 | document.addEventListener('spfdone', function (e) { 217 | if (${prefs.moreDetails}) { 218 | let button = document.querySelector('#action-panel-details button'); 219 | button.click(); 220 | } 221 | }); 222 | `; 223 | 224 | 225 | document.documentElement.appendChild(script); 226 | if (document.readyState !== 'loading') { 227 | let player = document.getElementById('movie_player') || document.getElementById('movie_player-flash'); 228 | if (player) { 229 | onYouTubePlayerReady(player); 230 | } 231 | } 232 | }); 233 | -------------------------------------------------------------------------------- /XUL/src/data/icon-16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/inbasic/iyccenter/aed528cdf04d858751c7f150911a4c141b649392/XUL/src/data/icon-16.png -------------------------------------------------------------------------------- /XUL/src/data/icon-32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/inbasic/iyccenter/aed528cdf04d858751c7f150911a4c141b649392/XUL/src/data/icon-32.png -------------------------------------------------------------------------------- /XUL/src/data/notification.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/inbasic/iyccenter/aed528cdf04d858751c7f150911a4c141b649392/XUL/src/data/notification.png -------------------------------------------------------------------------------- /XUL/src/data/overlay.css: -------------------------------------------------------------------------------- 1 | #iycenter, 2 | #iycenter[state="0"] { 3 | list-style-image: url('icon-16.png') !important; 4 | -moz-image-region: rect(0, 48px, 16px, 32px); 5 | } 6 | #iycenter[state="1"] { 7 | -moz-image-region: rect(0, 32px, 16px, 16px); 8 | } 9 | #iycenter[state="2"] { 10 | -moz-image-region: rect(0, 16px, 16px, 0); 11 | } 12 | /** Australis **/ 13 | #iycenter[cui-areatype="menu-panel"], 14 | toolbarpaletteitem[place="palette"] > #iycenter, 15 | #iycenter[cui-areatype="menu-panel"][state="0"], 16 | toolbarpaletteitem[place="palette"] > #iycenter[state="0"] { 17 | list-style-image: url('icon-32.png') !important; 18 | -moz-image-region: rect(0, 96px, 32px, 64px); 19 | } 20 | #iycenter[cui-areatype="menu-panel"][state="1"], 21 | toolbarpaletteitem[place="palette"] > #iycenter[state="1"] { 22 | -moz-image-region: rect(0, 64px, 32px, 32px); 23 | } 24 | #iycenter[cui-areatype="menu-panel"][state="2"], 25 | toolbarpaletteitem[place="palette"] > #iycenter[state="2"] { 26 | -moz-image-region: rect(0, 32px, 32px, 0); 27 | } 28 | /** Retina Display **/ 29 | @media (min-resolution: 2dppx) { 30 | #iycenter, 31 | #iycenter[state="0"] { 32 | list-style-image: url('icon-32.png') !important; 33 | -moz-image-region: rect(0, 96px, 32px, 64px); 34 | } 35 | #iycenter[state="1"] { 36 | -moz-image-region: rect(0, 64px, 32px, 32px); 37 | } 38 | #iycenter[state="2"] { 39 | -moz-image-region: rect(0, 32px, 32px, 0); 40 | } 41 | 42 | #iycenter image { 43 | width: 16px; 44 | } 45 | } -------------------------------------------------------------------------------- /XUL/src/data/player/close.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/inbasic/iyccenter/aed528cdf04d858751c7f150911a4c141b649392/XUL/src/data/player/close.png -------------------------------------------------------------------------------- /XUL/src/data/player/controls-12.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/inbasic/iyccenter/aed528cdf04d858751c7f150911a4c141b649392/XUL/src/data/player/controls-12.png -------------------------------------------------------------------------------- /XUL/src/data/player/controls-32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/inbasic/iyccenter/aed528cdf04d858751c7f150911a4c141b649392/XUL/src/data/player/controls-32.png -------------------------------------------------------------------------------- /XUL/src/data/player/controls-48.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/inbasic/iyccenter/aed528cdf04d858751c7f150911a4c141b649392/XUL/src/data/player/controls-48.png -------------------------------------------------------------------------------- /XUL/src/data/player/grippy_large.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/inbasic/iyccenter/aed528cdf04d858751c7f150911a4c141b649392/XUL/src/data/player/grippy_large.png -------------------------------------------------------------------------------- /XUL/src/data/player/history.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/inbasic/iyccenter/aed528cdf04d858751c7f150911a4c141b649392/XUL/src/data/player/history.png -------------------------------------------------------------------------------- /XUL/src/data/player/load.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/inbasic/iyccenter/aed528cdf04d858751c7f150911a4c141b649392/XUL/src/data/player/load.gif -------------------------------------------------------------------------------- /XUL/src/data/player/mp.css: -------------------------------------------------------------------------------- 1 | @import url(https://fonts.googleapis.com/css?family=Open+Sans); 2 | html { 3 | height: 100%; 4 | } 5 | body { 6 | display: -moz-box; 7 | -moz-box-orient: vertical; 8 | font-family: "Open Sans"; 9 | width: calc(100% - 10px); 10 | margin: 5px; 11 | overflow: hidden; 12 | background-color: #fff; 13 | -moz-user-select: none; 14 | height: calc(100% - 10px); 15 | cursor: default; 16 | } 17 | body, table, input { 18 | font-family: Roboto,arial,sans-serif; 19 | font-size: 12px; 20 | } 21 | hr { 22 | margin: 10px 0; 23 | border: none; 24 | border-top: dotted 1px rgba(48,184,237,0.7);; 25 | } 26 | /* Controls */ 27 | #controls { 28 | display: -moz-box; 29 | -moz-box-align: center; 30 | padding: 5px; 31 | } 32 | #controls i { 33 | display: block; 34 | opacity: 0.7; 35 | cursor: pointer; 36 | padding: 1px; 37 | width: 32px; 38 | height: 32px; 39 | margin-right: 3px; 40 | } 41 | #controls #play-button { 42 | background: -moz-image-rect(url(controls-48.png), 0, 48, 48, 0) center no-repeat; 43 | width: 48px; 44 | height: 48px; 45 | } 46 | #controls #play-button[paused=true] { 47 | background: -moz-image-rect(url(controls-48.png), 0, 96, 48, 48) center no-repeat; 48 | } 49 | #controls #previous-button { 50 | display: none; 51 | background: -moz-image-rect(url(controls-32.png), 0, 32, 32, 0) center no-repeat; 52 | } 53 | #controls #stop-button { 54 | background: -moz-image-rect(url(controls-32.png), 0, 64, 32, 32) center no-repeat; 55 | } 56 | #controls #next-button { 57 | display: none; 58 | background: -moz-image-rect(url(controls-32.png), 0, 96, 32, 64) center no-repeat; 59 | } 60 | #controls #previous-list { 61 | margin-left: 10px; 62 | background: -moz-image-rect(url(controls-32.png), 0, 160, 32, 128) center no-repeat; 63 | } 64 | #controls #previous-list[disabled=true]{ 65 | opacity: 0.3; 66 | } 67 | #controls #next-list { 68 | background: -moz-image-rect(url(controls-32.png), 0, 192, 32, 160) center no-repeat; 69 | } 70 | #controls #next-list[disabled=true] { 71 | opacity: 0.3; 72 | } 73 | #controls #volume-down { 74 | margin-left: 10px; 75 | background: -moz-image-rect(url(controls-32.png), 0, 256, 32, 224) center no-repeat; 76 | } 77 | #controls #volume-up { 78 | background: -moz-image-rect(url(controls-32.png), 0, 224, 32, 192) center no-repeat; 79 | } 80 | #controls div { 81 | display: -moz-box; 82 | -moz-box-flex: 1.0; 83 | -moz-box-pack: end; 84 | } 85 | #controls #settings-button { 86 | background: -moz-image-rect(url(controls-48.png), 0, 144, 48, 96) center no-repeat; 87 | width: 48px; 88 | height: 48px; 89 | } 90 | #controls i:hover:not([disabled=true]) { 91 | opacity: 1.0; 92 | } 93 | /* Play List */ 94 | #play-list { 95 | display: -moz-box; 96 | -moz-box-orient: vertical; 97 | -moz-box-flex: 1.0; 98 | } 99 | #history { 100 | width: 100%; 101 | } 102 | #history tr { 103 | background: rgba(243, 243, 243, 0.85); 104 | color: #222222; 105 | outline: thin solid transparent; 106 | font-size: 13px; 107 | cursor: pointer; 108 | line-height: 24px; 109 | display: none; 110 | } 111 | #history tr.over { 112 | outline: thin solid #999999; 113 | } 114 | #history tr.over-initiated { 115 | opacity: 0.4; 116 | } 117 | #history td:not(:nth-child(6)) { /* 6th child is close button */ 118 | pointer-events:none; 119 | } 120 | #history td:nth-child(1) { /* move */ 121 | width: 6px; 122 | cursor: move; 123 | display: none; 124 | } 125 | #history tr:hover td:nth-child(1) { 126 | background: url("grippy_large.png"); 127 | } 128 | #history td:nth-child(2) { /* play */ 129 | width: 12px; 130 | opacity: 0.4; 131 | } 132 | #history tr:not([playing="null"]) td:nth-child(2) { 133 | background: -moz-image-rect(url(controls-12.png), 0, 36, 12, 27) center no-repeat; 134 | } 135 | #history tr:hover td:nth-child(2) { 136 | background: -moz-image-rect(url(controls-12.png), 0, 27, 12, 18) center no-repeat; 137 | } 138 | #history tr[playing="1"] td:nth-child(2) { 139 | background: -moz-image-rect(url(controls-12.png), 0, 18, 12, 9) center no-repeat; 140 | } 141 | #history tr[playing="2"] td:nth-child(2) { 142 | background: -moz-image-rect(url(controls-12.png), 0, 9, 12, 0) center no-repeat; 143 | } 144 | #history td:nth-child(3) { /* title */ 145 | overflow: hidden; 146 | text-overflow: ellipsis; 147 | white-space: nowrap; 148 | max-width: 200px; 149 | } 150 | #history td:nth-child(4) { /* duration */ 151 | width: 80px; 152 | max-width: 80px; 153 | text-align: center; 154 | } 155 | #history tr td:nth-child(5) { 156 | display: none; 157 | width: 20px; 158 | opacity: 0.4; 159 | } 160 | #history tr[type=history] td:nth-child(5) { /* type */ 161 | display: table-cell; 162 | background: -moz-image-rect(url(history.png), 0, 12, 12, 0) center no-repeat; 163 | } 164 | #history tr[type=internet] td:nth-child(5) { 165 | display: table-cell; 166 | background: -moz-image-rect(url(history.png), 0, 24, 12, 12) center no-repeat; 167 | } 168 | #history td:nth-child(6) { /* close */ 169 | width: 20px; 170 | } 171 | #history tr:hover td:nth-child(6) { 172 | background: url('close.png') no-repeat center; 173 | } 174 | /* Search Box */ 175 | #search-div { 176 | display: -moz-box; 177 | -moz-box-pack: end; 178 | -moz-box-align: center; 179 | padding-top: 5px; 180 | } 181 | #search-div input { 182 | border: solid 1px rgba(48,184,237,0.7); 183 | width: 200px; 184 | padding: 3px; 185 | border-radius: 2px; 186 | padding-right: 24px; 187 | background: url('search.png') no-repeat calc(100% - 4px) center; 188 | } 189 | #search-div input:focus { 190 | border: solid 1px rgba(48,184,237,1); 191 | } 192 | #search-div input[loading] { 193 | background-image: url('load.gif'); 194 | } 195 | #controls-div { 196 | 197 | } 198 | -------------------------------------------------------------------------------- /XUL/src/data/player/mp.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 |
10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 |
19 | 20 |
21 |
22 | 23 |
24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 |
36 |
37 | 38 |
39 | 40 |
41 |
42 |
43 | 44 |
45 | 46 |
47 | 48 | 49 | -------------------------------------------------------------------------------- /XUL/src/data/player/mp.js: -------------------------------------------------------------------------------- 1 | var currentOffset = 0; 2 | 3 | function $(id) { 4 | $[id] = $[id] || document.getElementById(id); 5 | return $[id]; 6 | } 7 | /** Play list **/ 8 | $("history").addEventListener("click",function (e) { 9 | var target = e.originalTarget; 10 | if (target.localName === "td") { 11 | self.port.emit("kill", target.parentNode.getAttribute("videoID")); 12 | } 13 | else if (target.localName === "tr") { 14 | self.port.emit( 15 | target.getAttribute("playing") == "1" ? "pause" : "play", 16 | target.getAttribute("videoID") 17 | ); 18 | } 19 | }, false); 20 | 21 | 22 | document.addEventListener("change", function (e) { 23 | let target = e.target; 24 | let pref = target.dataset.pref; 25 | if (pref) { 26 | self.port.emit('prefs', { 27 | name: pref, 28 | value: target.checked 29 | }); 30 | } 31 | }); 32 | self.port.on('prefs', function (obj) { 33 | var elem = document.querySelector(`[data-pref="${obj.name}"]`); 34 | if (elem) { 35 | elem.checked = obj.value; 36 | } 37 | }); 38 | Array.from(document.querySelectorAll('[data-pref]')).forEach(e => self.port.emit('prefs', { 39 | name: e.dataset.pref, 40 | value: null 41 | })); 42 | /* 43 | var move = (function () { 44 | var dragged; 45 | var isTr = e => e.originalTarget.localName === "tr"; 46 | var paint = (e, type, name) => e.originalTarget.classList[type](name); 47 | 48 | return { 49 | drag: { 50 | start: function (e) { 51 | if (!isTr(e)) return; 52 | e.dataTransfer.setData('text/html', null); 53 | paint(e, "add", "over-initiated"); 54 | dragged = e; 55 | }, 56 | over: (e) => e.preventDefault(), 57 | enter: (e) => paint(e, "add", "over"), 58 | leave: (e) => paint(e, "remove", "over"), 59 | }, 60 | drop: function (e) { 61 | e.preventDefault(); 62 | paint(dragged, "remove", "over-initiated"); 63 | paint(e, "remove", "over"); 64 | 65 | var childs = $("history").getElementsByTagName("tr"); 66 | var i = Array.prototype.indexOf.call(childs, dragged.originalTarget); 67 | var j = Array.prototype.indexOf.call(childs, e.originalTarget); 68 | console.error(i, j); 69 | } 70 | } 71 | })(); 72 | $("history").addEventListener("dragstart", move.drag.start, false); 73 | $("history").addEventListener("dragover", move.drag.over, false); 74 | $("history").addEventListener("dragenter", move.drag.enter, false); 75 | $("history").addEventListener("dragleave", move.drag.leave, false); 76 | $("history").addEventListener("drop", move.drop, false); 77 | */ 78 | /** Search and Filter **/ 79 | function update(arr, doClean) { 80 | //Updating buttons 81 | $("play-button").setAttribute("paused", arr.reduce((p, c) => p || c.state == 1 , false)); 82 | //Updating list 83 | var trs = $("history").getElementsByTagName("tr"); 84 | arr = arr.splice(0, trs.length); 85 | arr.forEach(function (o, i) { 86 | var tr = trs[i], tds = tr.getElementsByTagName("td"); 87 | tr.style = "display: table-row"; 88 | if (o.type) { 89 | tr.setAttribute("type", o.type); 90 | } 91 | else { 92 | tr.removeAttribute("type"); 93 | } 94 | tr.setAttribute("videoID", o.id); 95 | tr.setAttribute("title", o.title); 96 | tr.setAttribute("playing", o.state); 97 | tds[2].textContent = o.title; 98 | tds[3].textContent = (new Date(1970,1,1,0,0,o.duration)).toTimeString().substr(0,8); 99 | }); 100 | if (doClean) { 101 | [].slice.call(trs, arr.length).forEach(tr => tr.style = "display: none"); 102 | } 103 | } 104 | $("search-box").addEventListener("input", function () { 105 | if ($("search-box").value) { 106 | self.port.emit("search", $("search-box").value); 107 | } 108 | else { 109 | self.port.emit("update"); 110 | } 111 | }, false); 112 | self.port.on("search", arr => update(arr, true)); 113 | 114 | /** Controls **/ 115 | $("play-button").addEventListener("click", function () { 116 | if ($("play-button").getAttribute("paused") == "true") { 117 | self.port.emit("pause-all"); 118 | } 119 | else { 120 | var trs = $("history").getElementsByTagName("tr"); 121 | var tr = [].reduce.call (trs, (p, c) => p || (c.getAttribute("playing") == 2 ? c : null), null) || trs[0]; 122 | self.port.emit("play", tr.getAttribute("videoID")); 123 | } 124 | }, false); 125 | $("stop-button").addEventListener("click", () => self.port.emit("stop-all")); 126 | $("next-list").addEventListener("click", function (e) { 127 | if (e.originalTarget.getAttribute("disabled") == "true") return; 128 | self.port.emit("update", "p"); 129 | }); 130 | $("previous-list").addEventListener("click", function (e) { 131 | if (e.originalTarget.getAttribute("disabled") == "true") return; 132 | self.port.emit("update", "n"); 133 | }); 134 | $("settings-button").addEventListener("click", () => self.port.emit("settings")); 135 | $("volume-down").addEventListener("click", () => self.port.emit("volume", "n")); 136 | $("volume-up").addEventListener("click", () => self.port.emit("volume", "p")); 137 | // 138 | self.port.on("update", function (o, isPrevious, isNext) { 139 | update(o, true); 140 | $("search-box").value = ""; 141 | $("previous-list").setAttribute("disabled", !isPrevious); 142 | $("next-list").setAttribute("disabled", !isNext); 143 | }); 144 | -------------------------------------------------------------------------------- /XUL/src/data/player/search.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/inbasic/iyccenter/aed528cdf04d858751c7f150911a4c141b649392/XUL/src/data/player/search.png -------------------------------------------------------------------------------- /XUL/src/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/inbasic/iyccenter/aed528cdf04d858751c7f150911a4c141b649392/XUL/src/icon.png -------------------------------------------------------------------------------- /XUL/src/icon64.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/inbasic/iyccenter/aed528cdf04d858751c7f150911a4c141b649392/XUL/src/icon64.png -------------------------------------------------------------------------------- /XUL/src/iyccenter.xpi: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/inbasic/iyccenter/aed528cdf04d858751c7f150911a4c141b649392/XUL/src/iyccenter.xpi -------------------------------------------------------------------------------- /XUL/src/lib/config.js: -------------------------------------------------------------------------------- 1 | exports.configs = { 2 | search: { 3 | url: "https://www.googleapis.com/youtube/v3/search?part=snippet&q=%q&type=video&key=AIzaSyAlphXgkjG-MXvlYnnzQv6cneRUepZA0uw", 4 | sql: 4, 5 | }, 6 | play: { 7 | url: "https://www.youtube.com/watch?v=", 8 | def: "https://www.youtube.com" 9 | }, 10 | panel: { 11 | width: 400, 12 | height: 370, 13 | numbers: 7 14 | }, 15 | toolbar: { 16 | id: "iycenter", 17 | move: { 18 | toolbarID: "nav-bar", 19 | insertbefore: "home-button", 20 | forceMove: false 21 | } 22 | }, 23 | extension: { 24 | url: "http://firefox.add0n.com/control-center.html", 25 | time: 3000 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /XUL/src/lib/http.js: -------------------------------------------------------------------------------- 1 | /**/ 2 | 'use strict'; 3 | 4 | var unload = require('sdk/system/unload'), 5 | sp = require('sdk/simple-prefs'), 6 | prefs = sp.prefs; 7 | // 8 | var {WebRequest} = require('resource://gre/modules/WebRequest.jsm'); 9 | var {MatchPattern} = require('resource://gre/modules/MatchPattern.jsm'); 10 | 11 | // autoplay next track 12 | var playlist = { 13 | isInstalled: false, 14 | listen: function (e) { 15 | if (e.url.endsWith('watch_autoplayrenderer.js')) { 16 | return {cancel: true}; 17 | } 18 | }, 19 | install: () => { 20 | if (!playlist.isInstalled) { 21 | WebRequest.onBeforeRequest.addListener(playlist.listen, 22 | { 23 | urls: new MatchPattern([ 24 | 'http://s.ytimg.com/yts/jsbin/*', 25 | 'https://s.ytimg.com/yts/jsbin/*' 26 | ]) 27 | }, 28 | ['blocking'] 29 | ); 30 | playlist.isInstalled = true; 31 | } 32 | }, 33 | uninstall: () => { 34 | if (playlist.isInstalled) { 35 | WebRequest.onBeforeRequest.removeListener(playlist.listen); 36 | playlist.isInstalled = false; 37 | } 38 | } 39 | }; 40 | unload.when(playlist.uninstall); 41 | if (!prefs.playlist) { 42 | playlist.install(); 43 | } 44 | sp.on('playlist', () => { 45 | playlist[prefs.playlist ? 'uninstall' : 'install'](); 46 | }); 47 | // autoplay embedded 48 | var autoplay = { 49 | isInstalled: false, 50 | listen: function (e) { 51 | if (e.url.indexOf('autoplay=1') !== -1) { 52 | return { 53 | redirectUrl: e.url.replace('autoplay=1', 'autoplay=0') 54 | }; 55 | } 56 | }, 57 | install: () => { 58 | if (!autoplay.isInstalled) { 59 | WebRequest.onBeforeSendHeaders.addListener(autoplay.listen, 60 | { 61 | urls: new MatchPattern([ 62 | 'http://www.youtube.com/embed/*', 63 | 'https://www.youtube.com/embed/*' 64 | ]) 65 | }, 66 | ['blocking'] 67 | ); 68 | autoplay.isInstalled = true; 69 | } 70 | }, 71 | uninstall: () => { 72 | if (autoplay.isInstalled) { 73 | WebRequest.onBeforeSendHeaders.removeListener(autoplay.listen); 74 | autoplay.isInstalled = false; 75 | } 76 | } 77 | }; 78 | unload.when(autoplay.uninstall); 79 | if (!prefs.autoplay) { 80 | autoplay.install(); 81 | } 82 | sp.on('autoplay', () => { 83 | autoplay[prefs.autoplay ? 'uninstall' : 'install'](); 84 | }); 85 | -------------------------------------------------------------------------------- /XUL/src/lib/main.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var tabs = require('sdk/tabs'), 4 | self = require('sdk/self'), 5 | data = self.data, 6 | {Cc, Ci} = require('chrome'), 7 | {all, defer} = require('sdk/core/promise'), 8 | notifications = require('sdk/notifications'), 9 | pageMod = require('sdk/page-mod'), 10 | Request = require('sdk/request').Request, 11 | _ = require('sdk/l10n').get, 12 | timer = require('sdk/timers'), 13 | panel = require('sdk/panel'), 14 | tabs = require('sdk/tabs'), 15 | sp = require('sdk/simple-prefs'), 16 | prefs = sp.prefs, 17 | http = require('./http'), // jshint ignore:line 18 | userstyles = require('./userstyles'), 19 | storage = require('./storage'), 20 | c = require('./config').configs, 21 | windows = { 22 | get active () { // Chrome window 23 | return require('sdk/window/utils').getMostRecentBrowserWindow(); 24 | } 25 | }, 26 | isAustralis = 'gCustomizeMode' in windows.active, 27 | toolbarbutton = require(isAustralis ? './toolbarbutton/new' : './toolbarbutton/old'); 28 | 29 | /** Loading styles **/ 30 | userstyles.load(data.url('overlay.css')); 31 | 32 | /** Debugger **/ 33 | function debug (a, b, c) { 34 | console.error(a, b, c); 35 | } 36 | 37 | var mp, button; 38 | 39 | /** Notifier **/ 40 | var notify = (text) => { 41 | if (prefs.silent) { 42 | return; 43 | } 44 | notifications.notify({ 45 | title: 'YouTube Control Center', 46 | text, 47 | iconURL: self.data.url('notification.png') 48 | }); 49 | } 50 | 51 | /** Permanent injections **/ 52 | var workers = (function () { 53 | var cache = []; 54 | return { 55 | attach: function (worker) { 56 | worker.id = -1; 57 | worker.state = -1; 58 | cache.push(worker); 59 | }, 60 | detach: function (worker) { 61 | var i = cache.indexOf(worker); 62 | if (i !== -1) { 63 | cache.splice(i, 1); 64 | } 65 | button.update(cache.map(c => c.state)); 66 | }, 67 | update: function (worker, id, state) { 68 | var i = cache.indexOf(worker); 69 | if (i !== -1) { 70 | cache[i].id = id; 71 | if (state) { 72 | cache[i].state = state; 73 | } 74 | if (state === 1) { 75 | mp.offset = 0; 76 | } 77 | mp.update(); 78 | button.update(cache.map(c => c.state)); 79 | } 80 | }, 81 | get: () => cache 82 | }; 83 | })(); 84 | 85 | pageMod.PageMod({ 86 | include: ['*.youtube.com'], 87 | contentScriptFile: data.url('end.js'), 88 | contentScriptWhen: 'start', 89 | attachTo: ['existing', 'top'], 90 | onAttach: function(worker) { 91 | workers.attach(worker); 92 | worker.port.emit('prefs', prefs); 93 | worker.on('detach', () => workers.detach(worker)); 94 | worker.port.emit('options', worker.tab.options); 95 | worker.port.on('info', function(o) { 96 | if (!o.id) { 97 | return; 98 | } 99 | workers.update(worker, o.id, -1); //-1: unstarted 100 | storage.insert(o.id, o.title, o.duration).then(); 101 | }); 102 | worker.port.on('onStateChange', function (id, state) { 103 | workers.update(worker, id, state); 104 | if (prefs.onePlayer && state === 1) { 105 | workers.get().filter((w) => (w.state === 1 && w.id !== id ? true : false)) 106 | .forEach((w) => w.port.emit('pause')); 107 | } 108 | if (prefs.loop && state === 0) { 109 | workers.get().filter((w) => w.id === id ? true : false).forEach(function (w) { 110 | timer.setTimeout((w) => w.port.emit('play'), prefs.loopDelay * 1000, w); 111 | }); 112 | } 113 | }); 114 | } 115 | }); 116 | 117 | /** Toolbar Panel **/ 118 | mp = panel.Panel({ 119 | width: c.panel.width, 120 | height: c.panel.height, 121 | contentURL: data.url('player/mp.html'), 122 | contentScriptFile: data.url('player/mp.js') 123 | }); 124 | mp.update = function(offset) { 125 | if (!mp.isShowing) { 126 | return; 127 | } 128 | if (offset === 'p' || offset === 'n') { 129 | mp.offset += (offset === 'p' ? 1 : -1) * c.panel.numbers; 130 | } 131 | storage.read(c.panel.numbers + 1, mp.offset).then( 132 | function (objs) { 133 | var isNext = objs.length === c.panel.numbers + 1; 134 | if (isNext) { 135 | objs.pop(); 136 | } 137 | objs.forEach( 138 | (o, i) => objs[i].state = workers.get().reduce((p, c) => c.id === o.id && (p === -1 || p === null) ? c.state : p, null) 139 | ); 140 | mp.port.emit('update', objs, mp.offset > 0, isNext); 141 | }, 142 | debug 143 | ); 144 | }; 145 | mp.offset = 0; 146 | mp.on('show', mp.update); 147 | mp.port.on('update', mp.update); 148 | mp.port.on('search', function (q) { 149 | all([storage.search(q, c.search.sql, workers.get()), search(q, c.panel.numbers)]).then( 150 | ([arr1, arr2]) => mp.port.emit('search', arr1.concat(arr2.splice(0, c.panel.numbers - arr1.length))), 151 | debug 152 | ); 153 | }); 154 | 155 | function pauseAll () { 156 | return workers.get().forEach((w) => w.port.emit('pause')); 157 | } 158 | mp.port.on('pause-all', pauseAll); 159 | mp.port.on('stop-all', function () { 160 | workers.get().forEach((w) => w.port.emit('stop')); 161 | }); 162 | function play (id) { //id === -1 to play an open tab 163 | var worker = workers.get().reduce((p, c) => p || (c && c.id === id ? c : null), null); 164 | if (id === -1) { 165 | worker = workers.get().filter(w => w.id !== -1).shift(); 166 | } 167 | if (worker) { 168 | return worker.port.emit('play'); 169 | } 170 | for (let tab of tabs) { 171 | if(/youtube\.com\/watch\?v\=/.test(tab.url) && id && id !== -1) { 172 | tab.options = {autoplay: true}; 173 | return tab.attach({ 174 | contentScript: 'window.location.replace("watch?v=' + id + '&autoplay=1");' 175 | }); 176 | } 177 | } 178 | tabs.open({ 179 | url: id && id !== -1 ? c.play.url + id + '&autoplay=1': c.play.def, 180 | }); 181 | } 182 | mp.port.on('play', play); 183 | mp.port.on('pause', function (id) { 184 | var worker = workers.get().reduce((p, c) => p || (c && c.id === id ? c : null), null); 185 | if (worker) { 186 | worker.port.emit('pause'); 187 | } 188 | }); 189 | mp.port.on('volume', function (v) { 190 | var volume = prefs.volume; 191 | prefs.volume += (v === 'p' && volume <= 90 ? 10 : 0) - (v === 'n' && volume >= 10 ? 10 : 0); 192 | workers.get().forEach((w) => w.port.emit('volume', prefs.volume)); 193 | notify(_('msg1') + ' ' + prefs.volume + '%'); 194 | }); 195 | function skip () { 196 | workers.get().filter(w => w.state === 1).forEach(w => w.port.emit('skip')); 197 | } 198 | mp.port.on('skip', skip); 199 | mp.port.on('kill', function (id) { 200 | storage.kill(id).then(mp.update); 201 | var tab = workers.get().reduce((p, c) => p || (c && c.id === id ? c.tab : null), null); 202 | if (tab) { 203 | tab.close(); 204 | } 205 | }); 206 | mp.port.on('settings', function () { 207 | windows.active.BrowserOpenAddonsMgr('addons://detail/' + self.id); 208 | mp.hide(); 209 | }); 210 | mp.port.on('prefs', function (obj) { 211 | if (obj.value === null) { 212 | mp.port.emit('prefs', { 213 | name: obj.name, 214 | value: prefs[obj.name] 215 | }); 216 | } 217 | else { 218 | prefs[obj.name] = obj.value; 219 | } 220 | }); 221 | sp.on('autoplay', () => { 222 | mp.port.emit('prefs', { 223 | name: 'autoplay', 224 | value: prefs.autoplay 225 | }); 226 | }); 227 | sp.on('loop', () => { 228 | mp.port.emit('prefs', { 229 | name: 'loop', 230 | value: prefs.loop 231 | }); 232 | }); 233 | 234 | /** Toolbar Button **/ 235 | button = toolbarbutton.ToolbarButton({ 236 | id: c.toolbar.id, 237 | label: _('toolbar'), 238 | tooltiptext: _('tooltip'), 239 | panel: mp, 240 | onCommand: (e, tbb) => mp.show(tbb), 241 | onClick: function (e, tbb) { 242 | if (e.button !== 1) { 243 | return; 244 | } 245 | e.stopPropagation(); 246 | e.preventDefault(); 247 | switch (prefs.middle) { 248 | case 1: return mp.show(tbb); 249 | case 2: return pauseAll(); 250 | case 3: return play(-1); 251 | case 4: return skip(); 252 | } 253 | }, 254 | onContext: function (e, tbb) { 255 | if (prefs.context) { 256 | e.stopPropagation(); 257 | e.preventDefault(); 258 | } 259 | switch (prefs.context) { 260 | case 1: return mp.show(tbb); 261 | case 2: return pauseAll(); 262 | case 3: return play(-1); 263 | case 4: return skip(); 264 | } 265 | } 266 | }); 267 | button.update = function (states) { 268 | button.state = states.reduce((p, c) => (c === 1 ? c : null) || p || (c === 2 ? c : null), null); 269 | }; 270 | /** YouTube Search **/ 271 | function search (q, num) { 272 | var d = defer(); 273 | if (q) { 274 | new Request({ 275 | url: c.search.url.replace('%q', q), 276 | headers: { 277 | Referer: 'https://www.youtube.com/' 278 | }, 279 | onComplete: function (r) { 280 | d.resolve((r.json && r.json.items ? r.json.items : []).splice(0, num).map(function (i) { 281 | let id = i.id.videoId; 282 | return { 283 | id, 284 | duration: 0, 285 | title: i.snippet.title, 286 | state: workers.get().reduce((p, c) => c.id === id ? c.state : p, null), 287 | type: 'internet' 288 | }; 289 | })); 290 | } 291 | }).get(); 292 | } 293 | else { 294 | d.resolve([]); 295 | } 296 | return d.promise; 297 | } 298 | /** Load and Unload **/ 299 | exports.main = function(options) { 300 | if (options.loadReason === 'install' || prefs.forceVisible) { 301 | button.moveTo(c.toolbar.move); 302 | } 303 | //Welcome 304 | if (options.loadReason === 'upgrade' || options.loadReason === 'install') { 305 | prefs.newVer = options.loadReason; 306 | } 307 | if (options.loadReason === 'startup' || options.loadReason === 'install') { 308 | welcome(); 309 | } 310 | }; 311 | exports.onUnload = function () { 312 | if (prefs.killOnExit) { 313 | storage.killall().then(storage.release); 314 | } 315 | else { 316 | storage.release(); 317 | } 318 | }; 319 | 320 | /** Options **/ 321 | sp.on('reset', function () { 322 | let pservice = Cc['@mozilla.org/preferences-service;1']. 323 | getService(Ci.nsIPrefService). 324 | getBranch('extensions.jid1-CikLKKPVkw6ipw@jetpack.'); 325 | pservice.getChildList('',{}) 326 | .filter(n => n !== 'version' && n.indexOf('sdk') === -1) 327 | .forEach(n => pservice.clearUserPref(n)); 328 | }); 329 | sp.on('clearLog', function () { 330 | if (!windows.active.confirm(_('msg2'))) { 331 | return; 332 | } 333 | storage.killall().then( () => notify(_('msg3'))); 334 | }); 335 | sp.on('autofshow', function () { 336 | if (prefs.autofshow) { 337 | prefs.autohide = false; 338 | prefs.controls = true; 339 | } 340 | }); 341 | sp.on('autohide', function () { 342 | if (prefs.autohide) { 343 | prefs.autofshow = false; 344 | } 345 | }); 346 | sp.on('playlist', function () { 347 | if (prefs.playlist) { 348 | prefs.autoplay = true; 349 | prefs.rel = true; 350 | } 351 | }); 352 | sp.on('rel', function () { 353 | if (!prefs.rel) { 354 | prefs.playlist = false; 355 | } 356 | }); 357 | 358 | /** Welcome page **/ 359 | function welcome () { 360 | if (!prefs.newVer) { 361 | return; 362 | } 363 | timer.setTimeout(function () { 364 | if (prefs.faqs) { 365 | tabs.open({ 366 | url: c.extension.url + '?v=' + self.version + '&type=' + prefs.newVer, 367 | inBackground : false 368 | }); 369 | } 370 | prefs.newVer = ''; 371 | }, c.extension.time); 372 | } 373 | -------------------------------------------------------------------------------- /XUL/src/lib/storage.js: -------------------------------------------------------------------------------- 1 | var _ = require("sdk/l10n").get, 2 | {Cc, Ci, Cu} = require('chrome'); 3 | 4 | Cu.import("resource://gre/modules/Promise.jsm"); 5 | Cu.import("resource://gre/modules/Services.jsm"); 6 | Cu.import("resource://gre/modules/FileUtils.jsm"); 7 | if (!Promise.all) { //Support for FF 24.0 8 | Promise = (function () { 9 | var obj = {}; 10 | for (var prop in Promise) { 11 | if(Promise.hasOwnProperty(prop)) { 12 | obj[prop] = Promise[prop]; 13 | } 14 | } 15 | obj.all = function (arr) { 16 | var d = new Promise.defer(), results = [], stage = arr.length; 17 | function next (succeed, i, result) { 18 | results[i] = result; 19 | stage -= 1; 20 | if (!succeed) d.reject(result); 21 | if (!stage) d.resolve(results); 22 | } 23 | arr.forEach((e, i) => e.then(next.bind(this, true, i), next.bind(this,false, i))); 24 | return d.promise; 25 | } 26 | Object.freeze(obj); 27 | return obj; 28 | })(); 29 | } 30 | 31 | var dbConn; 32 | function exec (index) { 33 | if (!dbConn) { 34 | dbConn = Services.storage.openDatabase( 35 | FileUtils.getFile("ProfD", ["youtube-control-center", "storage.sqlite"]) 36 | ); 37 | } 38 | var s = [ 39 | "CREATE TABLE IF NOT EXISTS list(" + 40 | " id CHAR(40) PRIMARY KEY NOT NULL," + 41 | " title TEXT NOT NULL," + 42 | " duration INTEGER NOT NULL," + 43 | " count INTEGER DEFAULT 0," + 44 | " time TIMESTAMP default (datetime('now', 'localtime'))" + 45 | ");", 46 | "INSERT OR IGNORE INTO list (id, title, duration) VALUES(?1, ?2, ?3);", 47 | "UPDATE list SET count = count + 1, time = DATETIME('now', 'localtime') WHERE id = ?1 AND time < DATETIME('now', 'localtime', '-1 Minute');", 48 | "SELECT * FROM list ORDER BY time DESC LIMIT ?1 OFFSET ?2", 49 | "DELETE FROM list WHERE id = ?1", 50 | "DELETE FROM list", 51 | "SELECT * FROM list WHERE title LIKE ?1 AND title LIKE ?2 AND title LIKE ?3 AND title LIKE ?4 AND title LIKE ?5 AND title LIKE ?6 ORDER BY time DESC LIMIT ?7" 52 | ]; 53 | 54 | var d = new Promise.defer(); 55 | var statement = dbConn.createStatement(s[index]); 56 | [].slice.call(arguments, 1).forEach((arg, i) => statement.bindUTF8StringParameter(i, arg)); 57 | statement.executeAsync({ 58 | handleResult: d.resolve, //returns result set 59 | handleError: d.reject, 60 | handleCompletion: function(reason) { 61 | if (reason != Ci.mozIStorageStatementCallback.REASON_FINISHED) { 62 | return d.reject(Error(_("err1"))); 63 | } 64 | d.resolve(); 65 | } 66 | }); 67 | return d.promise; 68 | } 69 | 70 | exports.insert = function (id, title, duration) { 71 | return Promise.all([exec(0), exec(1, id, title, duration), exec(2, id)]); 72 | } 73 | 74 | exports.kill = function (id) { 75 | return Promise.all([exec(0), exec(4, id)]); 76 | } 77 | 78 | exports.killall = function (id) { 79 | return Promise.all([exec(0), exec(5)]); 80 | } 81 | 82 | exports.search = function (q, num, workers) { 83 | var d = new Promise.defer(); 84 | 85 | q = q.replace(/\s{2,}/g, " ").split(" ").reverse().splice(0, 6); 86 | var searches = [6].concat(Array(6).join(" ").split(" ").map((s,i)=> "%" + (q[i] || "") + "%")).concat(num); 87 | 88 | Promise.all([exec(0), exec.apply(this, searches)]).then(function ([r0, set]) { 89 | var tmp = []; 90 | if (set) { 91 | for (var row; row = set.getNextRow();) { 92 | tmp.push({ 93 | id: row.getResultByName("id"), 94 | title: row.getResultByName("title"), 95 | duration: row.getResultByName("duration"), 96 | count: row.getResultByName("count"), 97 | time: row.getResultByName("time"), 98 | type: "history", 99 | state: workers.reduce((p, c) => c.id === row.getResultByName("id") ? c.state : p, null) 100 | }); 101 | } 102 | } 103 | d.resolve(tmp); 104 | }, d.reject); 105 | return d.promise; 106 | } 107 | 108 | exports.read = function (num, offset) { //Maximum number of columns to read 109 | var d = new Promise.defer(); 110 | Promise.all([exec(0), exec(3, num, offset)]).then(function ([r0, set]) { 111 | var tmp = []; 112 | if (set) { 113 | for (var row; row = set.getNextRow();) { 114 | tmp.push({ 115 | id: row.getResultByName("id"), 116 | title: row.getResultByName("title"), 117 | duration: row.getResultByName("duration"), 118 | count: row.getResultByName("count"), 119 | time: row.getResultByName("time") 120 | }); 121 | } 122 | } 123 | d.resolve(tmp); 124 | }, d.reject); 125 | return d.promise; 126 | } 127 | 128 | exports.release = function () { 129 | if (!dbConn) return; 130 | dbConn.mozIStorageConnection.asyncClose({ 131 | complete: function() {} 132 | }); 133 | } -------------------------------------------------------------------------------- /XUL/src/lib/toolbarbutton/new.js: -------------------------------------------------------------------------------- 1 | exports.ToolbarButton = function (options) { 2 | var {Cu} = require('chrome'), 3 | utils = require('sdk/window/utils'); 4 | Cu.import("resource:///modules/CustomizableUI.jsm"); 5 | 6 | var listen = { 7 | onWidgetBeforeDOMChange: function(tbb, aNextNode, aContainer, aIsRemoval) { 8 | if (tbb.id != options.id) return; 9 | if (tbb.isInstalled) return; 10 | tbb.isInstalled = true; 11 | 12 | tbb.addEventListener("command", function(e) { 13 | if (e.ctrlKey) return; 14 | if (e.originalTarget.localName == "menu" || e.originalTarget.localName == "menuitem") return; 15 | 16 | if (options.onCommand) { 17 | options.onCommand(e, tbb); 18 | } 19 | 20 | if (options.panel) { 21 | options.panel.show(tbb); 22 | } 23 | }, true); 24 | if (options.onClick) { 25 | tbb.addEventListener("click", function (e) { 26 | options.onClick(e, tbb); 27 | return true; 28 | }); 29 | } 30 | if (options.onContext) { 31 | tbb.addEventListener("contextmenu", function (e) { 32 | options.onContext(e, tbb); 33 | }, true); 34 | } 35 | } 36 | } 37 | CustomizableUI.addListener(listen); 38 | 39 | var getButton = () => utils.getMostRecentBrowserWindow().document.getElementById(options.id); 40 | var button = CustomizableUI.createWidget({ 41 | id : options.id, 42 | defaultArea : CustomizableUI.AREA_NAVBAR, 43 | label : options.label, 44 | tooltiptext : options.tooltiptext 45 | }); 46 | 47 | //Destroy on unload 48 | require("sdk/system/unload").when(function () { 49 | CustomizableUI.removeListener(listen); 50 | CustomizableUI.destroyWidget(options.id); 51 | }); 52 | 53 | return { 54 | destroy: function () { 55 | CustomizableUI.destroyWidget(options.id); 56 | }, 57 | moveTo: function () { 58 | 59 | }, 60 | get label() button.label, 61 | set label(value) { 62 | button.instances.forEach(function (i) { 63 | var tbb = i.anchor.ownerDocument.defaultView.document.getElementById(options.id); 64 | tbb.setAttribute("label", value); 65 | }); 66 | }, 67 | get tooltiptext() button.tooltiptext, 68 | set tooltiptext(value) { 69 | button.instances.forEach(function (i) { 70 | var tbb = i.anchor.ownerDocument.defaultView.document.getElementById(options.id); 71 | tbb.setAttribute("tooltiptext", value); 72 | }); 73 | }, 74 | set state(value) { 75 | button.instances.forEach(function (i) { 76 | var tbb = i.anchor.ownerDocument.defaultView.document.getElementById(options.id); 77 | if (!tbb) return; 78 | if (value) { 79 | tbb.setAttribute("state", value); 80 | } 81 | else { 82 | tbb.removeAttribute("state"); 83 | } 84 | }); 85 | }, 86 | get object () { 87 | return getButton(); 88 | } 89 | } 90 | } -------------------------------------------------------------------------------- /XUL/src/lib/toolbarbutton/old.js: -------------------------------------------------------------------------------- 1 | const NS_XUL = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"; 2 | 3 | var prefs = require("sdk/simple-prefs").prefs, 4 | winUtils = require("sdk/deprecated/window-utils"), 5 | utils = require('sdk/window/utils'); 6 | 7 | const browserURL = "chrome://browser/content/browser.xul"; 8 | 9 | /** unload+.js [start] **/ 10 | var unload = (function () { 11 | var unloaders = []; 12 | 13 | function unloadersUnlaod() { 14 | unloaders.slice().forEach(function(unloader) unloader()); 15 | unloaders.length = 0; 16 | } 17 | 18 | require("sdk/system/unload").when(unloadersUnlaod); 19 | 20 | function removeUnloader(unloader) { 21 | let index = unloaders.indexOf(unloader); 22 | if (index != -1) 23 | unloaders.splice(index, 1); 24 | } 25 | 26 | return { 27 | unload: function unload(callback, container) { 28 | // Calling with no arguments runs all the unloader callbacks 29 | if (callback == null) { 30 | unloadersUnlaod(); 31 | return null; 32 | } 33 | 34 | var remover = removeUnloader.bind(null, unloader); 35 | 36 | // The callback is bound to the lifetime of the container if we have one 37 | if (container != null) { 38 | // Remove the unloader when the container unloads 39 | container.addEventListener("unload", remover, false); 40 | 41 | // Wrap the callback to additionally remove the unload listener 42 | let origCallback = callback; 43 | callback = function() { 44 | container.removeEventListener("unload", remover, false); 45 | origCallback(); 46 | } 47 | } 48 | 49 | // Wrap the callback in a function that ignores failures 50 | function unloader() { 51 | try { 52 | callback(); 53 | } 54 | catch(ex) {} 55 | } 56 | unloaders.push(unloader); 57 | 58 | // Provide a way to remove the unloader 59 | return remover; 60 | } 61 | }; 62 | })().unload; 63 | /** unload+.js [end] **/ 64 | /** listen.js [start] **/ 65 | var listen = function listen(window, node, event, func, capture) { 66 | // Default to use capture 67 | if (capture == null) 68 | capture = true; 69 | 70 | node.addEventListener(event, func, capture); 71 | function undoListen() { 72 | node.removeEventListener(event, func, capture); 73 | } 74 | 75 | // Undo the listener on unload and provide a way to undo everything 76 | let undoUnload = unload(undoListen, window); 77 | return function() { 78 | undoListen(); 79 | undoUnload(); 80 | }; 81 | } 82 | /** listen.js [end] **/ 83 | 84 | exports.ToolbarButton = function ToolbarButton(options) { 85 | var unloaders = [], 86 | toolbarID = prefs.toolbarID || "", 87 | insertbefore = prefs.nextSibling || "", 88 | destroyed = false, 89 | destoryFuncs = []; 90 | 91 | var delegate = { 92 | onTrack: function (window) { 93 | if ("chrome://browser/content/browser.xul" != window.location || destroyed) 94 | return; 95 | 96 | let doc = window.document; 97 | let $ = function(id) doc.getElementById(id); 98 | options.tooltiptext = options.tooltiptext || ''; 99 | // create toolbar button 100 | let tbb = doc.createElementNS(NS_XUL, "toolbarbutton"); 101 | tbb.setAttribute("id", options.id); 102 | tbb.setAttribute("value", ""); 103 | tbb.setAttribute("class", "toolbarbutton-1 chromeclass-toolbar-additional"); 104 | tbb.setAttribute("label", options.label); 105 | tbb.setAttribute('tooltiptext', options.tooltiptext); 106 | tbb.addEventListener("command", function(e) { 107 | if (e.ctrlKey) return; 108 | if (e.originalTarget.localName == "menu" || e.originalTarget.localName == "menuitem") return; 109 | 110 | if (options.onCommand) 111 | options.onCommand(e, tbb); 112 | 113 | if (options.panel) { 114 | options.panel.show(tbb); 115 | } 116 | }, true); 117 | if (options.onClick) { 118 | tbb.addEventListener("click", function (e) { 119 | options.onClick(e, tbb); 120 | }, true); 121 | } 122 | if (options.onContext) { 123 | tbb.addEventListener("contextmenu", function (e) { 124 | options.onContext(e, tbb); 125 | }, true); 126 | } 127 | // add toolbarbutton to palette 128 | ($("navigator-toolbox") || $("mail-toolbox")).palette.appendChild(tbb); 129 | 130 | // find a toolbar to insert the toolbarbutton into 131 | if (toolbarID) { 132 | var tb = $(toolbarID); 133 | } 134 | if (!tb) { 135 | var tb = toolbarbuttonExists(doc, options.id); 136 | } 137 | 138 | // found a toolbar to use? 139 | if (tb) { 140 | let b4; 141 | 142 | // find the toolbarbutton to insert before 143 | if (insertbefore) { 144 | b4 = $(insertbefore); 145 | } 146 | if (!b4) { 147 | let currentset = tb.getAttribute("currentset").split(","); 148 | let i = currentset.indexOf(options.id) + 1; 149 | 150 | // was the toolbarbutton id found in the curent set? 151 | if (i > 0) { 152 | let len = currentset.length; 153 | // find a toolbarbutton to the right which actually exists 154 | for (; i < len; i++) { 155 | b4 = $(currentset[i]); 156 | if (b4) break; 157 | } 158 | } 159 | if (!b4) b4 = $("home-button"); 160 | } 161 | 162 | tb.insertItem(options.id, b4, null, false); 163 | } 164 | 165 | var saveTBNodeInfo = function(e) { 166 | toolbarID = tbb.parentNode.getAttribute("id") || ""; 167 | insertbefore = (tbb.nextSibling || "") 168 | && tbb.nextSibling.getAttribute("id").replace(/^wrapper-/i, ""); 169 | 170 | prefs.nextSibling = insertbefore; 171 | prefs.toolbarID = toolbarID; 172 | }; 173 | 174 | window.addEventListener("aftercustomization", saveTBNodeInfo, false); 175 | 176 | // add unloader to unload+'s queue 177 | var unloadFunc = function() { 178 | tbb.parentNode.removeChild(tbb); 179 | window.removeEventListener("aftercustomization", saveTBNodeInfo, false); 180 | }; 181 | var index = destoryFuncs.push(unloadFunc) - 1; 182 | listen(window, window, "unload", function() { 183 | destoryFuncs[index] = null; 184 | }, false); 185 | unloaders.push(unload(unloadFunc, window)); 186 | }, 187 | onUntrack: function (window) {} 188 | }; 189 | var tracker = winUtils.WindowTracker(delegate); 190 | function setState(aOptions) { 191 | getToolbarButtons(function(tbb) { 192 | if (aOptions.value) { 193 | tbb.setAttribute("state", aOptions.value); 194 | } 195 | else { 196 | tbb.removeAttribute("state"); 197 | } 198 | }, options.id); 199 | return aOptions.value; 200 | } 201 | 202 | return { 203 | destroy: function() { 204 | if (destroyed) return; 205 | destroyed = true; 206 | 207 | if (options.panel) 208 | options.panel.destroy(); 209 | 210 | // run unload functions 211 | destoryFuncs.forEach(function(f) f && f()); 212 | destoryFuncs.length = 0; 213 | 214 | // remove unload functions from unload+'s queue 215 | unloaders.forEach(function(f) f()); 216 | unloaders.length = 0; 217 | }, 218 | moveTo: function(pos) { 219 | if (destroyed) return; 220 | 221 | // record the new position for future windows 222 | toolbarID = prefs.toolbarID || pos.toolbarID; 223 | insertbefore = prefs.nextSibling || pos.insertbefore; 224 | 225 | if (toolbarID == "BrowserToolbarPalette") { 226 | toolbarID = "nav-bar"; 227 | insertbefore = "home-button"; 228 | } 229 | 230 | // change the current position for open windows 231 | for (let window of utils.windows()) { 232 | if (browserURL != window.location) return; 233 | 234 | let doc = window.document; 235 | let $ = function (id) doc.getElementById(id); 236 | 237 | // if the move isn't being forced and it is already in the window, abort 238 | if (!pos.forceMove && $(options.id)) return; 239 | 240 | var tb = $(toolbarID); 241 | var b4 = $(insertbefore); 242 | 243 | if (tb) { 244 | tb.insertItem(options.id, b4, null, false); 245 | tb.setAttribute("currentset", tb.currentSet); 246 | doc.persist(tb.id, "currentset"); 247 | } 248 | } 249 | }, 250 | get label() options.label, 251 | set label(value) { 252 | options.label = value; 253 | getToolbarButtons(function(tbb) { 254 | tbb.label = value; 255 | }, options.id); 256 | return value; 257 | }, 258 | set state (value) { 259 | setState({value: value}); 260 | }, 261 | get tooltiptext() options.tooltiptext, 262 | set tooltiptext(value) { 263 | options.tooltiptext = value; 264 | getToolbarButtons(function(tbb) { 265 | tbb.setAttribute('tooltiptext', value); 266 | }, options.id); 267 | }, 268 | get object () { 269 | return utils.getMostRecentBrowserWindow().document.getElementById(options.id); 270 | } 271 | }; 272 | }; 273 | 274 | function getToolbarButtons(callback, id) { 275 | let buttons = []; 276 | for (let window of utils.windows()) { 277 | if (browserURL != window.location) continue; 278 | let tbb = window.document.getElementById(id); 279 | if (tbb) buttons.push(tbb); 280 | } 281 | if (callback) buttons.forEach(callback); 282 | return buttons; 283 | } 284 | 285 | function toolbarbuttonExists(doc, id) { 286 | var toolbars = doc.getElementsByTagNameNS(NS_XUL, "toolbar"); 287 | for (var i = toolbars.length - 1; ~i; i--) { 288 | if ((new RegExp("(?:^|,)" + id + "(?:,|$)")).test(toolbars[i].getAttribute("currentset"))) 289 | return toolbars[i]; 290 | } 291 | return false; 292 | } 293 | -------------------------------------------------------------------------------- /XUL/src/lib/userstyles.js: -------------------------------------------------------------------------------- 1 | /* This Source Code Form is subject to the terms of the Mozilla Public 2 | * License, v. 2.0. If a copy of the MPL was not distributed with this 3 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 4 | "use strict"; 5 | 6 | const { Cc, Ci } = require("chrome"); 7 | const unload = require("sdk/system/unload").when; 8 | 9 | const sss = Cc["@mozilla.org/content/style-sheet-service;1"] 10 | .getService(Ci.nsIStyleSheetService); 11 | 12 | function getURI(aURL) Cc["@mozilla.org/network/io-service;1"] 13 | .getService(Ci.nsIIOService).newURI(aURL, null, null); 14 | 15 | function setOptions(url, options) { 16 | let newOptions = {}; 17 | options = options || {}; 18 | 19 | newOptions.uri = getURI(url); 20 | newOptions.type = (options.type || 'user').toLowerCase(); 21 | newOptions.type = (newOptions.type == 'agent') ? sss.AGENT_SHEET : sss.USER_SHEET; 22 | 23 | return newOptions; 24 | }; 25 | 26 | /** 27 | * Load various packaged styles for the add-on and undo on unload 28 | * 29 | * @usage load(aURL): Load specified style 30 | * @param [string] aURL: Style file to load 31 | * @param [object] options: 32 | */ 33 | const loadSS = exports.load = function loadSS(url, options) { 34 | let { uri, type } = setOptions(url, options); 35 | 36 | // load the stylesheet 37 | sss.loadAndRegisterSheet(uri, type); 38 | 39 | // unload the stylesheet on unload 40 | unload(unregisterSS.bind(null, url, options)); 41 | }; 42 | 43 | const registeredSS = exports.registered = function registeredSS(url, options) { 44 | let { uri, type } = setOptions(url, options); 45 | 46 | // check that the stylesheet is registered 47 | return !!sss.sheetRegistered(uri, type); 48 | }; 49 | 50 | const unregisterSS = exports.unload = function unregisterSS(url, options) { 51 | let { uri, type } = setOptions(url, options); 52 | 53 | // unregister the stylesheet 54 | sss.unregisterSheet(uri, type); 55 | }; 56 | -------------------------------------------------------------------------------- /XUL/src/locale/en-US.properties: -------------------------------------------------------------------------------- 1 | err1 = storage.js::Query canceled or aborted! 2 | err2 = main.js>search::Could not connect to YouTube server 3 | 4 | name = YouTube Control Center 5 | toolbar = YouTube Control Center 6 | tooltip = YouTube Control Center 7 | 8 | msg1 = Player volume is 9 | msg2 = This will clear all the history on YouTube Control Center. Are you sure? 10 | msg3 = Logs have been cleared. 11 | 12 | # Options 13 | context_title = [UI] Right click on the toolbar button: 14 | context_options.default = Default (show Firefox context menu) 15 | context_options.mp = Show YouTube Control Center's panel 16 | context_options.pauseall = Pause all video players 17 | context_options.play = Start playing the most recent video 18 | context_options.skip = Skip to the next video (on playlist mode only) 19 | middle_title = [UI] Middle click on the toolbar button: 20 | middle_options.default = Default (do nothing) 21 | middle_options.mp = Show YouTube Control Center's panel 22 | middle_options.pauseall = Pause all video players 23 | middle_options.play = Start playing the most recent video 24 | middle_options.skip = Skip to the next video (on playlist mode only) 25 | quality_title = [Player] Suggested playback quality: 26 | quality_options.tiny = Tiny 27 | quality_options.small = Small 28 | quality_options.medium = Medium 29 | quality_options.large = Large 30 | quality_options.hd720 = HD720p 31 | quality_options.hd1080 = HD1080p 32 | quality_options.hd1440 = HD1440p 33 | quality_options.hd2160 = HD2160p 34 | quality_options.highres = High Resolution 35 | quality_options.default = Default 36 | size_title = [Player] Video player frame size: 37 | size_description = Video player size in pixels 38 | size_options.small = 320px by 240px 39 | size_options.medium = 640px by 360px 40 | size_options.large = 853px by 480px 41 | size_options.hd720 = 1280px by 720px 42 | size_options.hd1080 = 1920px by 1080px 43 | size_options.highres = 1920px by 1080px 44 | size_options.default = Default 45 | volume_title = [Player] Default video playback volume: 46 | volume_options.0 = Mute 47 | autoplay_title = [Player] Autoplay video when player loads (tab refresh required): 48 | autoplay_description = False value conflicts with 'automatically play next video from playlist' 49 | autobuffer_title = [Player] Autobuffer video even if player is not playing: 50 | controls_title = [Player] Show video controls on player (deprecated): 51 | autohide_title = [Player] Autohide video controls after a video begins playing: 52 | autofshow_title = [Player] Display video controls even in fullscreen mode: 53 | loop_title = [Player] Play videos again and again (loop mode) (tab refresh required): 54 | loopDelay_title = [Player] Delay between plays for loop mode (in seconds): 55 | disablekb_title = [Player] Disable keyboard controls (spacebar and arrow keys) on player: 56 | fs_title = [Player] Display fullscreen button on player: 57 | rel_title = [Player] Display related videos when playback of the current video ends: 58 | rel_description = False value conflicts with 'automatically play next video from playlist' 59 | theme_title = [Player] Display player controls (like a 'play' button or volume control) within a dark or light control bar (deprecated): 60 | theme_options.light = Light 61 | theme_options.dark = Dark 62 | color_title = [Player] Color of video progressbar: 63 | color_options.white = White 64 | color_options.red = Red 65 | header_title = [Page] Display "search bar": 66 | sidebar_title = [Page] Display "video suggestions" sidebar: 67 | discussion_title = [Page] Display "video comments" pane: 68 | views_title = [Page] Display "video views info" pane: 69 | actions_title = [Page] Display "action buttons" pane ('like', 'dislike', 'share', ... buttons): 70 | details_title = [Page] Display "details" pane (published info, buy video, ...): 71 | moreDetails_title = [Page] Automatically "Show more" on the video description: 72 | annotations_title = [Player] Show video annotations on player: 73 | onePlayer_title = [Misc.] Pause all YouTube players when the current player is playing: 74 | skipads_title = [Misc.] Skip advertisements in player: 75 | killOnExit_title = [Misc.] Clear tracks (logs) history on exit: 76 | clearLog_title = [Misc.] Clear tracks (logs) history: 77 | clearLog_label = Clear 78 | forceVisible_title = [Misc.] Always try to keep the toolbar button visible: 79 | playlist_title = [Player] Automatically play next video from playlist (Up Next): 80 | playlist_description = False value conflicts with 'autoplay video when player loads' and 'display related videos when playback of the video ends 81 | reset_title = [Misc.] Reset all settings to the defaults 82 | reset _label = Reset 83 | -------------------------------------------------------------------------------- /XUL/src/locale/pl-PL.properties: -------------------------------------------------------------------------------- 1 | err1 = storage.js::Zapytanie anulowane lub przerwane! 2 | err2 = main.js>search::Nie można było połączyć się z serwerem YouTube 3 | 4 | name = Centrum Kontroli YouTube 5 | toolbar = Centrum Kontroli YouTube 6 | tooltip = Centrum Kontroli YouTube 7 | 8 | msg1 = Głośność odtwarzacza to 9 | msg2 = To wyczyści całą historię w Centrum Kontroli Youtube. Jesteś pewien? 10 | msg3 = Logi zostały wyczyszczone. 11 | 12 | # Options 13 | context_title = [UI] Klikniecie prawym-PM na przycisk na pasku narzędzi: 14 | context_options.default = Domyślnie (pokaz menu kontekstowe Firefox) 15 | context_options.mp = Pokaz panel Centrum Kontroli YouTube 16 | context_options.pauseall = Zatrzymaj wszystkie odtwarzacze wideo 17 | context_options.play = Zacznij odtwarzać ostatnio wyświetlone wideo 18 | context_options.skip = Przeskocz do następnego wideo (tylko w trybie playlisty) 19 | middle_title = [UI] Klikniecie środkowym-PM na przycisk na pasku narzedzi: 20 | middle_options.default = Domyślne (nie rób nic) 21 | middle_options.mp = Pokaz panel Centrum Kontroli YouTube 22 | middle_options.pauseall = Zatrzymaj wszystkie odtwarzacze wideo 23 | middle_options.play = Zacznij odtwarzać ostatnio wyświetlone wideo 24 | middle_options.skip = Przeskocz do następnego wideo (tylko w trybie playlisty) 25 | quality_title = [Odtwarzacz] Sugerowana jakość odtwarzanego wideo: 26 | quality_options.small = Niska 27 | quality_options.medium = Średnia 28 | quality_options.large = Wysoka 29 | quality_options.hd720 = HD 720p 30 | quality_options.hd1080 = HD 1080p 31 | quality_options.highres = Wysoka Rozdzielczość 32 | quality_options.default = Domyślna 33 | size_title = [Odtwarzacz] Wielkość ramki odtwarzacza wideo: 34 | size_description = Rozmiar odtwarzacza wideo w pikselach 35 | size_options.small = 320px na 240px 36 | size_options.medium = 640px na 360px 37 | size_options.large = 853px na 480px 38 | size_options.hd720 = 1280px na 720px 39 | size_options.hd1080 = 1920px na 1080px 40 | size_options.highres = 1920px na 1080px 41 | size_options.default = Domyślnie 42 | volume_title = [Odtwarzacz] Głośność odtwarzanego wideo: 43 | volume_options.0 = Wycisz 44 | autoplay_title = [Odtwarzacz] Automatycznie odtwarzaj wideo, gdy załaduje się odtwarzacz: 45 | fnautoplay_title = [Odtwarzacz] Wymuś brak autoodtwarzania, gdy załaduje się odtwarzacz: 46 | fnautoplay_description = Zaznacz to TYLKO kiedy opcja powyżej u Ciebie nie działa (głównie dla odtwarzacza HTML5) 47 | autobuffer_title = [Odtwarzacz] Automatycznie buforuj wideo, nawet jeżeli wideo nie jest odtwarzane: 48 | controls_title = [Odtwarzacz] Pokazuj kontrolki wideo w odtwarzaczu: 49 | autohide_title = [Odtwarzacz] Automatycznie ukryj kontrolki wideo po rozpoczęciu odtwarzania: 50 | loop_title = [Odtwarzacz] Odtwarzaj wideo w kolko (pętla): 51 | loopDelay_title = [Odtwarzacz] Opóźnienie pomiędzy odtworzeniami w trybie pętli (w sekundach): 52 | disablekb_title = [Odtwarzacz] Wyłącz kontrolę klawiaturą (spacja i strzałki) w odtwarzaczu: 53 | fs_title = [Odtwarzacz] Pokazuj przycisk pełnego ekranu w odtwarzaczu: 54 | rel_title = [Odtwarzacz] Pokazuj powiązane filmy wideo na końcu odtwarzania: 55 | theme_title = [Odtwarzacz] Pokazuj kontrolki odtwarzacza (takie jak przycisk 'play' lub kontrola głośności) w ciemnym lub jasnym pasku sterowania: 56 | theme_options.light = Jasny 57 | theme_options.dark = Ciemny 58 | color_title = [Odtwarzacz] Kolor paska postępu wideo: 59 | color_options.white = Biały 60 | color_options.red = Czerwony 61 | sidebar_title = [Strona] Pokazuj sekcję "Wideo rekomendacje": 62 | discussion_title = [Strona] Pokazuj sekcję "Komentarze": 63 | views_title = [Strona] Pokazuj sekcję "Ilość odtworzeń": 64 | actions_title = [Strona] Pokazuj panel "przycisków akcji" (przyciski 'Lubie', 'Nie Lubie', 'Udostępnij', ... ): 65 | details_title = [Strona] Pokazuj sekcję "Szczegóły" (Opublikowano, kup wideo, ...): 66 | moreDetails_title = [Strona] Automatyczne "Pokaz więcej" w opisie wideo: 67 | annotations_title = [Strona] Pokazuj adnotacje w odtwarzaczu: 68 | onePlayer_title = [Różne] Zatrzymaj wszystkie odtwarzacze YouTube, gdy odtwarzany jest aktualny odtwarzacz 69 | skipads_title = [Różne] Pomiń reklamy w odtwarzaczu: 70 | killOnExit_title = [Różne] Wyczyść historie (logi) odtwarzania przy zamykaniu: 71 | clearLog_title = [Różne] Wyczyść historie (logi) odtwarzania: 72 | clearLog_label = Wyczyść 73 | forceVisible_title = [Różne] Zawsze staraj się trzymać pasek narzędzi widoczny: -------------------------------------------------------------------------------- /XUL/src/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "iyccenter", 3 | "title": "YouTube Control Center", 4 | "id": "jid1-CikLKKPVkw6ipw@jetpack", 5 | "description": "YouTube Control Center provides a set of useful tools for YouTube.com", 6 | "author": "InBasic", 7 | "license": "MPL 2.0", 8 | "version": "0.4.3", 9 | "main": "./lib/main.js", 10 | "permissions": { 11 | "unsafe-content-script": true, 12 | "private-browsing": true, 13 | "multiprocess": true 14 | }, 15 | 16 | "preferences": [ 17 | { 18 | "name": "context", 19 | "title": "[UI] Right click on the toolbar button", 20 | "type": "menulist", 21 | "value": 0, 22 | "options": [ 23 | {"value": "0", "label": "default"}, 24 | {"value": "1", "label": "mp"}, 25 | {"value": "2", "label": "pauseall"}, 26 | {"value": "3", "label": "play"}, 27 | {"value": "4", "label": "skip"} 28 | ] 29 | }, 30 | { 31 | "name": "middle", 32 | "title": "[UI] Middle click on the toolbar button:", 33 | "type": "menulist", 34 | "value": 0, 35 | "options": [ 36 | {"value": "0", "label": "default"}, 37 | {"value": "1", "label": "mp"}, 38 | {"value": "2", "label": "pauseall"}, 39 | {"value": "3", "label": "play"}, 40 | {"value": "4", "label": "skip"} 41 | ] 42 | }, 43 | { 44 | "name": "header", 45 | "title": "[Player] Suggested playback quality:", 46 | "type": "bool", 47 | "value": true 48 | }, 49 | { 50 | "name": "sidebar", 51 | "title": "[Page] Display 'video suggestions' sidebar:", 52 | "type": "bool", 53 | "value": true 54 | }, 55 | { 56 | "name": "discussion", 57 | "title": "[Page] Display 'video comments' pane:", 58 | "type": "bool", 59 | "value": true 60 | }, 61 | { 62 | "name": "views", 63 | "title": "[Page] Display 'video views info' pane:", 64 | "type": "bool", 65 | "value": true 66 | }, 67 | { 68 | "name": "actions", 69 | "title": "[Page] Display 'action buttons' pane ('like', 'dislike', 'share', ... buttons)", 70 | "type": "bool", 71 | "value": true 72 | }, 73 | { 74 | "name": "details", 75 | "title": "[Page] Display 'details' pane (published info, buy video, ...):", 76 | "type": "bool", 77 | "value": true 78 | }, 79 | { 80 | "name": "moreDetails", 81 | "title": "[Page] Automatically 'Show more' on the video description:", 82 | "type": "bool", 83 | "value": false 84 | }, 85 | { 86 | "name": "annotations", 87 | "title": "Player] Show video annotations on player:", 88 | "type": "bool", 89 | "value": true 90 | }, 91 | { 92 | "name": "quality", 93 | "title": "[Player] Suggested playback quality:", 94 | "type": "menulist", 95 | "value": 9, 96 | "options": [ 97 | {"value": "0", "label": "tiny"}, 98 | {"value": "1", "label": "small"}, 99 | {"value": "2", "label": "medium"}, 100 | {"value": "3", "label": "large"}, 101 | {"value": "4", "label": "hd720"}, 102 | {"value": "5", "label": "hd1080"}, 103 | {"value": "6", "label": "hd1440"}, 104 | {"value": "7", "label": "hd2160"}, 105 | {"value": "8", "label": "highres"}, 106 | {"value": "9", "label": "default"} 107 | ] 108 | }, 109 | { 110 | "name": "volume", 111 | "title": "[Player] Default video playback volume:", 112 | "type": "menulist", 113 | "value": 80, 114 | "options": [ 115 | {"value": "0", "label": "0"}, 116 | {"value": "10", "label": "10"}, 117 | {"value": "20", "label": "20"}, 118 | {"value": "30", "label": "30"}, 119 | {"value": "40", "label": "40"}, 120 | {"value": "50", "label": "50"}, 121 | {"value": "60", "label": "60"}, 122 | {"value": "70", "label": "70"}, 123 | {"value": "80", "label": "80"}, 124 | {"value": "90", "label": "90"}, 125 | {"value": "100", "label": "100"} 126 | ] 127 | }, 128 | { 129 | "name": "autoplay", 130 | "title": "[Player] Autoplay video when player loads ((tab refreshing is required):", 131 | "description": "False value conflicts with 'automatically play next video from playlist'", 132 | "type": "bool", 133 | "value": true 134 | }, 135 | { 136 | "name": "autobuffer", 137 | "title": "[Player] Autobuffer video even if player is not playing:", 138 | "type": "bool", 139 | "value": false 140 | }, 141 | { 142 | "name": "playlist", 143 | "title": "[Player] Automatically play next video from playlist (Up Next):", 144 | "description": "False value conflicts with 'autoplay video when player loads' and 'display related videos when playback of the video ends'", 145 | "type": "bool", 146 | "value": true 147 | }, 148 | { 149 | "name": "rel", 150 | "title": "[Player] Display related videos when playback of the current video ends:", 151 | "description": "False value conflicts with 'automatically play next video from playlist'", 152 | "type": "bool", 153 | "value": true 154 | }, 155 | { 156 | "name": "controls", 157 | "title": "[Player] Show video controls on player (deprecated):", 158 | "type": "bool", 159 | "value": true 160 | }, 161 | { 162 | "name": "autohide", 163 | "title": "[Player] Autohide video controls after a video begins playing:", 164 | "type": "bool", 165 | "value": false 166 | }, 167 | { 168 | "name": "autofshow", 169 | "title": "[Player] Display video controls even in fullscreen mode:", 170 | "type": "bool", 171 | "value": false 172 | }, 173 | { 174 | "name": "loop", 175 | "title": "[Player] Play videos again and again (loop mode) (tab refreshing is required):", 176 | "type": "bool", 177 | "value": false 178 | }, 179 | { 180 | "name": "loopDelay", 181 | "title": "[Player] Delay between plays for loop mode (in seconds):", 182 | "type": "integer", 183 | "value": 2 184 | }, 185 | { 186 | "name": "disablekb", 187 | "title": "[Player] Disable keyboard controls (spacebar and arrow keys) on player:", 188 | "type": "bool", 189 | "value": false 190 | }, 191 | { 192 | "name": "fs", 193 | "title": "Display fullscreen button on player:", 194 | "type": "bool", 195 | "value": true 196 | }, 197 | { 198 | "name": "theme", 199 | "title": "[Player] Display player controls (like a 'play' button or volume control) within a dark or light control bar (deprecated):", 200 | "type": "menulist", 201 | "value": 0, 202 | "options": [ 203 | {"value": "0", "label": "dark"}, 204 | {"value": "1", "label": "light"} 205 | ] 206 | }, 207 | { 208 | "name": "color", 209 | "title": "[Player] Color of video progressbar:", 210 | "type": "menulist", 211 | "value": 0, 212 | "options": [ 213 | {"value": "0", "label": "red"}, 214 | {"value": "1", "label": "white"} 215 | ] 216 | }, 217 | { 218 | "name": "onePlayer", 219 | "title": "[Misc.] Pause all YouTube players when the current player is playing:", 220 | "type": "bool", 221 | "value": true 222 | }, 223 | { 224 | "name": "skipads", 225 | "title": "[Misc.] Skip advertisements in player:", 226 | "type": "bool", 227 | "value": true 228 | }, 229 | { 230 | "name": "forceVisible", 231 | "title": "[Misc.] Always try to keep the toolbar button visible:", 232 | "type": "bool", 233 | "value": true 234 | }, 235 | { 236 | "name": "killOnExit", 237 | "title": "[Misc.] Clear tracks (logs) history on exit:", 238 | "type": "bool", 239 | "value": false 240 | }, 241 | { 242 | "name": "silent", 243 | "title": "[Misc.] Do not display desktop notifications:", 244 | "type": "bool", 245 | "value": false 246 | }, 247 | { 248 | "name": "clearLog", 249 | "type": "control", 250 | "label": "Clear", 251 | "title": "[Misc.] Clear tracks (logs) history:" 252 | }, 253 | { 254 | "name": "faqs", 255 | "title": "[Misc.] Display FAQs page upon upgrade:", 256 | "description": "Extension will display the FAQs page once per upgrade to notify you about the recent changes", 257 | "type": "bool", 258 | "value": true 259 | }, 260 | { 261 | "name": "reset", 262 | "type": "control", 263 | "label": "Reset", 264 | "title": "[Misc.] Reset all settings to the defaults" 265 | }] 266 | } 267 | -------------------------------------------------------------------------------- /drawings/icon.cdr: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/inbasic/iyccenter/aed528cdf04d858751c7f150911a4c141b649392/drawings/icon.cdr -------------------------------------------------------------------------------- /drawings/pause.cdr: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/inbasic/iyccenter/aed528cdf04d858751c7f150911a4c141b649392/drawings/pause.cdr -------------------------------------------------------------------------------- /drawings/play.cdr: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/inbasic/iyccenter/aed528cdf04d858751c7f150911a4c141b649392/drawings/play.cdr -------------------------------------------------------------------------------- /drawings/stop.cdr: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/inbasic/iyccenter/aed528cdf04d858751c7f150911a4c141b649392/drawings/stop.cdr --------------------------------------------------------------------------------