├── .gitattributes ├── .gitignore ├── Fatbros.ogg ├── LICENSE.txt ├── README.md ├── assets └── powered_by_white.png ├── css ├── normalize.css └── style.css ├── index.html ├── js ├── app2.js └── jquery-2.1.4.js ├── tests ├── spacebg.jpg ├── test01_tiles.html ├── test02_hexagons.html ├── test03_cubes.html ├── test03_cubes_optimized.html ├── test04_audio_analyzer.html ├── test05_audio_hexagons.html ├── test05a_colour_scale.html ├── test07_audio_hexagons_plus.html ├── test08_audio_hexagons_centered.html ├── test09_audio_hexagons_starfield.html ├── test10_audio_hexagons_starfield_2.html ├── test11_embedded_audio.html └── test12_soundcloud_visualizer.html └── webapi.html /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | 4 | # Custom for Visual Studio 5 | *.cs diff=csharp 6 | *.sln merge=union 7 | *.csproj merge=union 8 | *.vbproj merge=union 9 | *.fsproj merge=union 10 | *.dbproj merge=union 11 | 12 | # Standard to msysgit 13 | *.doc diff=astextplain 14 | *.DOC diff=astextplain 15 | *.docx diff=astextplain 16 | *.DOCX diff=astextplain 17 | *.dot diff=astextplain 18 | *.DOT diff=astextplain 19 | *.pdf diff=astextplain 20 | *.PDF diff=astextplain 21 | *.rtf diff=astextplain 22 | *.RTF diff=astextplain 23 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .idea 2 | 3 | ################# 4 | ## Eclipse 5 | ################# 6 | 7 | *.pydevproject 8 | .project 9 | .metadata 10 | bin/ 11 | tmp/ 12 | *.tmp 13 | *.bak 14 | *.swp 15 | *~.nib 16 | local.properties 17 | .classpath 18 | .settings/ 19 | .loadpath 20 | 21 | # External tool builders 22 | .externalToolBuilders/ 23 | 24 | # Locally stored "Eclipse launch configurations" 25 | *.launch 26 | 27 | # CDT-specific 28 | .cproject 29 | 30 | # PDT-specific 31 | .buildpath 32 | 33 | 34 | ################# 35 | ## Visual Studio 36 | ################# 37 | 38 | ## Ignore Visual Studio temporary files, build results, and 39 | ## files generated by popular Visual Studio add-ons. 40 | 41 | # User-specific files 42 | *.suo 43 | *.user 44 | *.sln.docstates 45 | 46 | # Build results 47 | 48 | [Dd]ebug/ 49 | [Rr]elease/ 50 | x64/ 51 | build/ 52 | [Bb]in/ 53 | [Oo]bj/ 54 | 55 | # MSTest test Results 56 | [Tt]est[Rr]esult*/ 57 | [Bb]uild[Ll]og.* 58 | 59 | *_i.c 60 | *_p.c 61 | *.ilk 62 | *.meta 63 | *.obj 64 | *.pch 65 | *.pdb 66 | *.pgc 67 | *.pgd 68 | *.rsp 69 | *.sbr 70 | *.tlb 71 | *.tli 72 | *.tlh 73 | *.tmp 74 | *.tmp_proj 75 | *.log 76 | *.vspscc 77 | *.vssscc 78 | .builds 79 | *.pidb 80 | *.log 81 | *.scc 82 | 83 | # Visual C++ cache files 84 | ipch/ 85 | *.aps 86 | *.ncb 87 | *.opensdf 88 | *.sdf 89 | *.cachefile 90 | 91 | # Visual Studio profiler 92 | *.psess 93 | *.vsp 94 | *.vspx 95 | 96 | # Guidance Automation Toolkit 97 | *.gpState 98 | 99 | # ReSharper is a .NET coding add-in 100 | _ReSharper*/ 101 | *.[Rr]e[Ss]harper 102 | 103 | # TeamCity is a build add-in 104 | _TeamCity* 105 | 106 | # DotCover is a Code Coverage Tool 107 | *.dotCover 108 | 109 | # NCrunch 110 | *.ncrunch* 111 | .*crunch*.local.xml 112 | 113 | # Installshield output folder 114 | [Ee]xpress/ 115 | 116 | # DocProject is a documentation generator add-in 117 | DocProject/buildhelp/ 118 | DocProject/Help/*.HxT 119 | DocProject/Help/*.HxC 120 | DocProject/Help/*.hhc 121 | DocProject/Help/*.hhk 122 | DocProject/Help/*.hhp 123 | DocProject/Help/Html2 124 | DocProject/Help/html 125 | 126 | # Click-Once directory 127 | publish/ 128 | 129 | # Publish Web Output 130 | *.Publish.xml 131 | *.pubxml 132 | 133 | # NuGet Packages Directory 134 | ## TODO: If you have NuGet Package Restore enabled, uncomment the next line 135 | #packages/ 136 | 137 | # Windows Azure Build Output 138 | csx 139 | *.build.csdef 140 | 141 | # Windows Store app package directory 142 | AppPackages/ 143 | 144 | # Others 145 | sql/ 146 | *.Cache 147 | ClientBin/ 148 | [Ss]tyle[Cc]op.* 149 | ~$* 150 | *~ 151 | *.dbmdl 152 | *.[Pp]ublish.xml 153 | *.pfx 154 | *.publishsettings 155 | 156 | # RIA/Silverlight projects 157 | Generated_Code/ 158 | 159 | # Backup & report files from converting an old project file to a newer 160 | # Visual Studio version. Backup files are not needed, because we have git ;-) 161 | _UpgradeReport_Files/ 162 | Backup*/ 163 | UpgradeLog*.XML 164 | UpgradeLog*.htm 165 | 166 | # SQL Server files 167 | App_Data/*.mdf 168 | App_Data/*.ldf 169 | 170 | ############# 171 | ## Windows detritus 172 | ############# 173 | 174 | # Windows image file caches 175 | Thumbs.db 176 | ehthumbs.db 177 | 178 | # Folder config file 179 | Desktop.ini 180 | 181 | # Recycle Bin used on file shares 182 | $RECYCLE.BIN/ 183 | 184 | # Mac crap 185 | .DS_Store 186 | 187 | 188 | ############# 189 | ## Python 190 | ############# 191 | 192 | *.py[co] 193 | 194 | # Packages 195 | *.egg 196 | *.egg-info 197 | dist/ 198 | build/ 199 | eggs/ 200 | parts/ 201 | var/ 202 | sdist/ 203 | develop-eggs/ 204 | .installed.cfg 205 | 206 | # Installer logs 207 | pip-log.txt 208 | 209 | # Unit test / coverage reports 210 | .coverage 211 | .tox 212 | 213 | #Translations 214 | *.mo 215 | 216 | #Mr Developer 217 | .mr.developer.cfg 218 | -------------------------------------------------------------------------------- /Fatbros.ogg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GabrielchenCN/yunba-wifi-bulb-example/1be46265c524f8b299a2890920402e0d391569a9/Fatbros.ogg -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2013 Michael Bromley 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 6 | 7 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 8 | 9 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## YunBa - Wi-Fi灯泡web端声控说明 2 | 3 | 使用 Web Audio API 和 [云巴智能灯泡](https://github.com/yunbaidea/yunbabulb)的Restful API,在web端通过声音控制云巴智能小灯泡的亮度和颜色。 4 | 5 | ## 使用方法 6 | 7 | ###安装 8 | 9 | 1. `git clone git@github.com:GabrielchenCN/yunba-wifi-bulb-example.git` 10 | 2. [使用nginx部署应用和跨域问题处理](http://www.cnblogs.com/gabrielchen/p/5066120.html) 11 | 12 | 13 | ###配置 14 | 15 | 1. 首先确保云巴灯泡有相应别名和正确连接上服务器,详见:[云巴智能灯泡](https://github.com/yunbaidea/yunbabulb)。 16 | 17 | 2. 在app2.js中配置frevolObj对象的正确别名。 18 | 19 | `var frevolObj={"Sarah":{"r":105,"g":"","b":255,"volume":0}}; ` 20 | 21 | 3. 因为是前端AJAX请求,所以直接请求url可能遇到跨域问题。设置`urlproxy`参数,可能遇到的[跨域访问问题解决方案](http://www.cnblogs.com/gabrielchen/p/5066120.html)。 22 | - 直接请求url以及参数设置如下: 23 | - alias: 别名 24 | - p : 默认1000 25 | - rgb : 分别为对应rgb值,范围0~22222,值越大亮度越大 26 | - http method: GET 27 | - ```var url = 'http://rest.yunba.io:8080?method=publish_to_alias&appkey=56556dd4f085fc471efe0688&seckey=sec-uTYMY37JTlH5hEmsvmgO8FkZYwkkPA45VAuDifUeQIsh4enS&alias='+config.alias+'&msg={"p":'+config.p+',"r":'+config.red+',"g":'+config.green+',"b":'+config.blue+'}';``` 28 | 29 | ###运行 30 | 31 | - 运行index.html 32 | - 建议使用firefox运行或47版本以下chrome,并允许浏览器共享麦克风。 33 | 34 | ###浏览器支持 35 | 36 | - chrome 47 以下(出于安全原因,47以上需要https协议访问[详见](https://sites.google.com/a/chromium.org/dev/Home/chromium-security/deprecating-powerful-features-on-insecure-origins)) 37 | - firefox 38 | 39 | 40 | ###相关引用 41 | 42 | - [soundcloud-visualizer](https://github.com/michaelbromley/soundcloud-visualizer) 43 | - [web audio api 简单开始](http://www.cnblogs.com/gabrielchen/p/5078760.html) 44 | -------------------------------------------------------------------------------- /assets/powered_by_white.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GabrielchenCN/yunba-wifi-bulb-example/1be46265c524f8b299a2890920402e0d391569a9/assets/powered_by_white.png -------------------------------------------------------------------------------- /css/normalize.css: -------------------------------------------------------------------------------- 1 | /*! normalize.css v2.1.3 | MIT License | git.io/normalize */ 2 | 3 | /* ========================================================================== 4 | HTML5 display definitions 5 | ========================================================================== */ 6 | 7 | /** 8 | * Correct `block` display not defined in IE 8/9. 9 | */ 10 | 11 | article, 12 | aside, 13 | details, 14 | figcaption, 15 | figure, 16 | footer, 17 | header, 18 | hgroup, 19 | main, 20 | nav, 21 | section, 22 | summary { 23 | display: block; 24 | } 25 | 26 | /** 27 | * Correct `inline-block` display not defined in IE 8/9. 28 | */ 29 | 30 | audio, 31 | canvas, 32 | video { 33 | display: inline-block; 34 | } 35 | 36 | /** 37 | * Prevent modern browsers from displaying `audio` without controls. 38 | * Remove excess height in iOS 5 devices. 39 | */ 40 | 41 | audio:not([controls]) { 42 | display: none; 43 | height: 0; 44 | } 45 | 46 | /** 47 | * Address `[hidden]` styling not present in IE 8/9. 48 | * Hide the `template` element in IE, Safari, and Firefox < 22. 49 | */ 50 | 51 | [hidden], 52 | template { 53 | display: none; 54 | } 55 | 56 | /* ========================================================================== 57 | Base 58 | ========================================================================== */ 59 | 60 | /** 61 | * 1. Set default font family to sans-serif. 62 | * 2. Prevent iOS text size adjust after orientation change, without disabling 63 | * user zoom. 64 | */ 65 | 66 | html { 67 | font-family: sans-serif; /* 1 */ 68 | -ms-text-size-adjust: 100%; /* 2 */ 69 | -webkit-text-size-adjust: 100%; /* 2 */ 70 | } 71 | 72 | /** 73 | * Remove default margin. 74 | */ 75 | 76 | body { 77 | margin: 0; 78 | } 79 | 80 | /* ========================================================================== 81 | Links 82 | ========================================================================== */ 83 | 84 | /** 85 | * Remove the gray background color from active links in IE 10. 86 | */ 87 | 88 | a { 89 | background: transparent; 90 | } 91 | 92 | /** 93 | * Address `outline` inconsistency between Chrome and other browsers. 94 | */ 95 | 96 | a:focus { 97 | outline: thin dotted; 98 | } 99 | 100 | /** 101 | * Improve readability when focused and also mouse hovered in all browsers. 102 | */ 103 | 104 | a:active, 105 | a:hover { 106 | outline: 0; 107 | } 108 | 109 | /* ========================================================================== 110 | Typography 111 | ========================================================================== */ 112 | 113 | /** 114 | * Address variable `h1` font-size and margin within `section` and `article` 115 | * contexts in Firefox 4+, Safari 5, and Chrome. 116 | */ 117 | 118 | h1 { 119 | font-size: 2em; 120 | margin: 0.67em 0; 121 | } 122 | 123 | /** 124 | * Address styling not present in IE 8/9, Safari 5, and Chrome. 125 | */ 126 | 127 | abbr[title] { 128 | border-bottom: 1px dotted; 129 | } 130 | 131 | /** 132 | * Address style set to `bolder` in Firefox 4+, Safari 5, and Chrome. 133 | */ 134 | 135 | b, 136 | strong { 137 | font-weight: bold; 138 | } 139 | 140 | /** 141 | * Address styling not present in Safari 5 and Chrome. 142 | */ 143 | 144 | dfn { 145 | font-style: italic; 146 | } 147 | 148 | /** 149 | * Address differences between Firefox and other browsers. 150 | */ 151 | 152 | hr { 153 | -moz-box-sizing: content-box; 154 | box-sizing: content-box; 155 | height: 0; 156 | } 157 | 158 | /** 159 | * Address styling not present in IE 8/9. 160 | */ 161 | 162 | mark { 163 | background: #ff0; 164 | color: #000; 165 | } 166 | 167 | /** 168 | * Correct font family set oddly in Safari 5 and Chrome. 169 | */ 170 | 171 | code, 172 | kbd, 173 | pre, 174 | samp { 175 | font-family: monospace, serif; 176 | font-size: 1em; 177 | } 178 | 179 | /** 180 | * Improve readability of pre-formatted text in all browsers. 181 | */ 182 | 183 | pre { 184 | white-space: pre-wrap; 185 | } 186 | 187 | /** 188 | * Set consistent quote types. 189 | */ 190 | 191 | q { 192 | quotes: "\201C" "\201D" "\2018" "\2019"; 193 | } 194 | 195 | /** 196 | * Address inconsistent and variable font size in all browsers. 197 | */ 198 | 199 | small { 200 | font-size: 80%; 201 | } 202 | 203 | /** 204 | * Prevent `sub` and `sup` affecting `line-height` in all browsers. 205 | */ 206 | 207 | sub, 208 | sup { 209 | font-size: 75%; 210 | line-height: 0; 211 | position: relative; 212 | vertical-align: baseline; 213 | } 214 | 215 | sup { 216 | top: -0.5em; 217 | } 218 | 219 | sub { 220 | bottom: -0.25em; 221 | } 222 | 223 | /* ========================================================================== 224 | Embedded content 225 | ========================================================================== */ 226 | 227 | /** 228 | * Remove border when inside `a` element in IE 8/9. 229 | */ 230 | 231 | img { 232 | border: 0; 233 | } 234 | 235 | /** 236 | * Correct overflow displayed oddly in IE 9. 237 | */ 238 | 239 | svg:not(:root) { 240 | overflow: hidden; 241 | } 242 | 243 | /* ========================================================================== 244 | Figures 245 | ========================================================================== */ 246 | 247 | /** 248 | * Address margin not present in IE 8/9 and Safari 5. 249 | */ 250 | 251 | figure { 252 | margin: 0; 253 | } 254 | 255 | /* ========================================================================== 256 | Forms 257 | ========================================================================== */ 258 | 259 | /** 260 | * Define consistent border, margin, and padding. 261 | */ 262 | 263 | fieldset { 264 | border: 1px solid #c0c0c0; 265 | margin: 0 2px; 266 | padding: 0.35em 0.625em 0.75em; 267 | } 268 | 269 | /** 270 | * 1. Correct `color` not being inherited in IE 8/9. 271 | * 2. Remove padding so people aren't caught out if they zero out fieldsets. 272 | */ 273 | 274 | legend { 275 | border: 0; /* 1 */ 276 | padding: 0; /* 2 */ 277 | } 278 | 279 | /** 280 | * 1. Correct font family not being inherited in all browsers. 281 | * 2. Correct font size not being inherited in all browsers. 282 | * 3. Address margins set differently in Firefox 4+, Safari 5, and Chrome. 283 | */ 284 | 285 | button, 286 | input, 287 | select, 288 | textarea { 289 | font-family: inherit; /* 1 */ 290 | font-size: 100%; /* 2 */ 291 | margin: 0; /* 3 */ 292 | } 293 | 294 | /** 295 | * Address Firefox 4+ setting `line-height` on `input` using `!important` in 296 | * the UA stylesheet. 297 | */ 298 | 299 | button, 300 | input { 301 | line-height: normal; 302 | } 303 | 304 | /** 305 | * Address inconsistent `text-transform` inheritance for `button` and `select`. 306 | * All other form control elements do not inherit `text-transform` values. 307 | * Correct `button` style inheritance in Chrome, Safari 5+, and IE 8+. 308 | * Correct `select` style inheritance in Firefox 4+ and Opera. 309 | */ 310 | 311 | button, 312 | select { 313 | text-transform: none; 314 | } 315 | 316 | /** 317 | * 1. Avoid the WebKit bug in Android 4.0.* where (2) destroys native `audio` 318 | * and `video` controls. 319 | * 2. Correct inability to style clickable `input` types in iOS. 320 | * 3. Improve usability and consistency of cursor style between image-type 321 | * `input` and others. 322 | */ 323 | 324 | button, 325 | html input[type="button"], /* 1 */ 326 | input[type="reset"], 327 | input[type="submit"] { 328 | -webkit-appearance: button; /* 2 */ 329 | cursor: pointer; /* 3 */ 330 | } 331 | 332 | /** 333 | * Re-set default cursor for disabled elements. 334 | */ 335 | 336 | button[disabled], 337 | html input[disabled] { 338 | cursor: default; 339 | } 340 | 341 | /** 342 | * 1. Address box sizing set to `content-box` in IE 8/9/10. 343 | * 2. Remove excess padding in IE 8/9/10. 344 | */ 345 | 346 | input[type="checkbox"], 347 | input[type="radio"] { 348 | box-sizing: border-box; /* 1 */ 349 | padding: 0; /* 2 */ 350 | } 351 | 352 | /** 353 | * 1. Address `appearance` set to `searchfield` in Safari 5 and Chrome. 354 | * 2. Address `box-sizing` set to `border-box` in Safari 5 and Chrome 355 | * (include `-moz` to future-proof). 356 | */ 357 | 358 | input[type="search"] { 359 | -webkit-appearance: textfield; /* 1 */ 360 | -moz-box-sizing: content-box; 361 | -webkit-box-sizing: content-box; /* 2 */ 362 | box-sizing: content-box; 363 | } 364 | 365 | /** 366 | * Remove inner padding and search cancel button in Safari 5 and Chrome 367 | * on OS X. 368 | */ 369 | 370 | input[type="search"]::-webkit-search-cancel-button, 371 | input[type="search"]::-webkit-search-decoration { 372 | -webkit-appearance: none; 373 | } 374 | 375 | /** 376 | * Remove inner padding and border in Firefox 4+. 377 | */ 378 | 379 | button::-moz-focus-inner, 380 | input::-moz-focus-inner { 381 | border: 0; 382 | padding: 0; 383 | } 384 | 385 | /** 386 | * 1. Remove default vertical scrollbar in IE 8/9. 387 | * 2. Improve readability and alignment in all browsers. 388 | */ 389 | 390 | textarea { 391 | overflow: auto; /* 1 */ 392 | vertical-align: top; /* 2 */ 393 | } 394 | 395 | /* ========================================================================== 396 | Tables 397 | ========================================================================== */ 398 | 399 | /** 400 | * Remove most spacing between table cells. 401 | */ 402 | 403 | table { 404 | border-collapse: collapse; 405 | border-spacing: 0; 406 | } -------------------------------------------------------------------------------- /css/style.css: -------------------------------------------------------------------------------- 1 | @font-face { 2 | font-family: 'icomoon'; 3 | src: url('fonts/icomoon.eot'); 4 | } 5 | @font-face { 6 | font-family: 'icomoon'; 7 | src: url(data:application/x-font-ttf;charset=utf-8;base64,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) format('truetype'), 8 | url(data:application/font-woff;charset=utf-8;base64,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) format('woff'); 9 | font-weight: normal; 10 | font-style: normal; 11 | } 12 | 13 | [class^="icon-"], [class*=" icon-"] { 14 | font-family: 'icomoon'; 15 | speak: none; 16 | font-style: normal; 17 | font-weight: normal; 18 | font-variant: normal; 19 | text-transform: none; 20 | line-height: 1; 21 | 22 | /* Better Font Rendering =========== */ 23 | -webkit-font-smoothing: antialiased; 24 | -moz-osx-font-smoothing: grayscale; 25 | } 26 | 27 | .icon-github:before { 28 | content: "\e600"; 29 | } 30 | .icon-play:before { 31 | content: "\e601"; 32 | } 33 | .icon-info:before { 34 | content: "\e602"; 35 | } 36 | .icon-menu:before { 37 | content: "\e603"; 38 | } 39 | 40 | * { 41 | padding: 0; 42 | margin: 0; 43 | } 44 | body { 45 | background-color: #000000; 46 | overflow: hidden; 47 | } 48 | a, a:visited { 49 | text-decoration: none; 50 | color: #c6c6c6; 51 | } 52 | a:hover, a:active { 53 | color: #fff; 54 | } 55 | #info { 56 | display: none; 57 | } 58 | #controlPanel { 59 | z-index: 50; 60 | position: absolute; 61 | bottom: 35px; 62 | width: 100%; 63 | color: #999; 64 | max-height: 600px; 65 | -webkit-transition: max-height 0.8s; 66 | -moz-transition: max-height 0.8s; 67 | transition: max-height 0.8s; 68 | overflow: hidden; 69 | } 70 | #controlPanel.hidden { 71 | max-height: 52px; 72 | } 73 | #tab { 74 | padding: 10px 16px 2px 16px; 75 | font-size: 32px; 76 | } 77 | #tab>a { 78 | background-color: rgba(255, 255, 255, 0.15); 79 | padding: 7px 10px 3px 10px; 80 | } 81 | #trackInfoPanel { 82 | float: left; 83 | width: 100%; 84 | overflow: hidden; 85 | max-height: 200px; 86 | -webkit-transition: max-height 0.8s; 87 | -moz-transition: max-height 0.8s; 88 | transition: max-height 0.8s; 89 | background-color: rgba(255, 255, 255, 0.15); 90 | padding-top: 10px; 91 | } 92 | #trackInfoPanel.hidden { 93 | max-height: 0px; 94 | -webkit-transition: max-height 0.2s; 95 | -moz-transition: max-height 0.2s; 96 | transition: max-height 0.2s; 97 | } 98 | #infoImage { 99 | float: left; 100 | padding: 0px 8px 8px 8px; 101 | } 102 | #infoArtist { 103 | font-size: 24px; 104 | } 105 | #infoTrack { 106 | font-size: 30px; 107 | } 108 | #playerControls { 109 | text-align: center; 110 | position: relative; 111 | width: 100%; 112 | float: left; 113 | background-color: rgba(255, 255, 255, 0.15); 114 | padding-top: 10px; 115 | } 116 | #input { 117 | font-size: 20px; 118 | padding: 8px; 119 | margin-bottom: 10px; 120 | margin-left: -30px; 121 | width: 80%; 122 | max-width: 690px; 123 | color: white; 124 | background-color: #EBEBEB; 125 | color: #333; 126 | border: 1px solid #111; 127 | -webkit-border-radius: 3px; 128 | -moz-border-radius: 3px; 129 | border-radius: 3px; 130 | } 131 | #submit { 132 | position: absolute; 133 | color: #fff; 134 | font-size: 32px; 135 | padding: 5px; 136 | background: none; 137 | border: none; 138 | } 139 | #player { 140 | width: 95%; 141 | } 142 | #messageBox { 143 | position: absolute; 144 | top: 30%; 145 | width: 100%; 146 | padding: 20px 0; 147 | text-align: center; 148 | background-color: rgba(255, 255, 255, 0.8); 149 | opacity: 1; 150 | max-height: 500px; 151 | -webkit-transition: opacity 0.5s, max-height 0.5s, padding 0.5s; 152 | -moz-transition: opacity 0.5s, max-height 0.5s, padding 0.5s; 153 | transition: opacity 0.5s, max-height 0.5s, padding 0.5s; 154 | z-index: 500; 155 | overflow: hidden; 156 | } 157 | #messageBox.hidden { 158 | opacity: 0; 159 | max-height: 0; 160 | padding: 0; 161 | } 162 | #messageBox p { 163 | margin-bottom: 10px; 164 | } 165 | #messageBox a:link, #messageBox a:visited { 166 | color: #666; 167 | } 168 | footer { 169 | position: absolute; 170 | bottom: 0; 171 | z-index: 100; 172 | background-color: #111; 173 | width: 100%; 174 | color: #999; 175 | height: 35px; 176 | } 177 | #credit, #github { 178 | display: inline-block; 179 | padding-top: 8px; 180 | padding-right: 10px; 181 | } 182 | #scLogo { 183 | float: right; 184 | } 185 | /* make some txt small for small screens */ 186 | @media (max-width: 900px) { 187 | footer { 188 | font-size: 12px; 189 | } 190 | } 191 | @media (max-width: 480px) { 192 | #input { 193 | font-size: 16px; 194 | } 195 | footer { 196 | font-size: 10px; 197 | } 198 | } -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Yunba 6 | 7 | 8 | 9 | 10 |
11 | 12 |
13 | 14 |
15 | 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /js/app2.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by Michael on 31/12/13. 3 | */ 4 | 5 | /** 6 | * The *AudioSource object creates an analyzer node, sets up a repeating function with setInterval 7 | * which samples the input and turns it into an FFT array. The object has two properties: 8 | * streamData - this is the Uint8Array containing the FFT data 9 | * volume - cumulative value of all the bins of the streaData. 10 | * 11 | * The MicrophoneAudioSource uses the getUserMedia interface to get real-time data from the user's microphone. Not used currently but included for possible future use. 12 | */ 13 | var MicrophoneAudioSource = function() { 14 | var self = this; 15 | this.volume = 0; 16 | this.streamData = new Uint8Array(128); 17 | var analyser; 18 | 19 | //全局变量监控声音是否调用,配置别名 20 | var frevolObj={ 21 | "Sarah":{"r":105,"g":"","b":255,"volume":0}, 22 | "Kathie":{"r":255,"g":"","b":255,"volume":0}, 23 | "Christina":{"r":204,"g":"","b":154,"volume":0}, 24 | "Frieda":{"r":204,"g":"","b":102,"volume":0}, 25 | "Jamie":{"r":255,"g":"","b":51,"volume":0} 26 | // "Sarah":{"r":0,"g":255,"b":153,"volume":0}, 27 | // "Kathie":{"r":0,"g":255,"b":30,"volume":0}, 28 | // "Christina":{"r":255,"g":255,"b":255,"volume":0}, 29 | // "Frieda":{"r":255,"g":255,"b":0,"volume":0}, 30 | // "Jamie":{"r":255,"g":51,"b":0,"volume":0} 31 | // "f":{"r":1,"g":"","b":1,"volume":0} 32 | 33 | }; 34 | 35 | //触发时间20*25=0.5秒 36 | var num= 10; 37 | 38 | var sampleAudioStream = function() { 39 | //把频域数据填入 40 | analyser.getByteFrequencyData(self.streamData); 41 | num--; 42 | 43 | // calculate an overall volume value 44 | var total = 0; 45 | for(var i in self.streamData) { 46 | 47 | total += self.streamData[i]; 48 | 49 | // console.log(self.streamData); 50 | } 51 | self.volume = total; 52 | 53 | 54 | //声音大小 55 | //console.log(self.volume); 56 | //console.log(frequencyVolume(100,120)); 57 | //setInterval(yunba("yunbaio",'1','0','0',frequencyVolume(50,70)),1000); 58 | // 声音判断逻辑: 59 | // 变量 frevolArr:保存某一频段的声音大小 60 | // 方法 frequencyVolume;获得某一频段的声音大小(区间0-128,由fftsize确定) 61 | // 方法 yunba :根据别名对某一灯泡传入rgb数据和frevol声音大小 62 | // 方法 closebulb 当所有频率声音大小self.volume小于300时,发送关闭灯泡请求 63 | 64 | frevolObj.Sarah.volume += frequencyVolume(0,20); 65 | frevolObj.Kathie.volume += frequencyVolume(21,40); 66 | frevolObj.Christina.volume += frequencyVolume(41,60); 67 | frevolObj.Frieda.volume += frequencyVolume(61,80); 68 | frevolObj.Jamie.volume += frequencyVolume(81,100); 69 | 70 | 71 | for(var key in frevolObj) 72 | { 73 | var tmp =frevolObj[key].volume; 74 | var r =frevolObj[key].r; 75 | var g =(Number(frevolObj[key].volume)/100).toFixed(0); 76 | var b = frevolObj[key].b; 77 | //调节tmp需要调节checkcv函数 78 | if (tmp>4000&&num<=6) { 79 | 80 | yunba(key,r,g,b,tmp); 81 | console.log(key+"---"+tmp+":rgb("+r+","+g+","+b+")"); 82 | frevolObj[key].volume=0; 83 | if (num==0) { 84 | num =25; 85 | }; 86 | 87 | } 88 | } 89 | 90 | 91 | }; 92 | //某频率段的音量大小 93 | var frequencyVolume=function(start,end){ 94 | var fv = 0; 95 | for (var i = start; i 42 ? 1.5 : 2; // increase this value to fade out faster. 266 | this.highlight = 0; // for highlighted stroke effect; 267 | // figure out the x and y coordinates of the center of the polygon based on the 268 | // 60 degree XY axis coordinates passed in 269 | var step = Math.round(Math.cos(Math.PI/6)*tileSize*2); 270 | this.y = Math.round(step * Math.sin(Math.PI/3) * -y ); 271 | this.x = Math.round(x * step + y * step/2 ); 272 | 273 | // calculate the vertices of the polygon 274 | this.vertices = []; 275 | for (var i = 1; i <= this.sides;i += 1) { 276 | x = this.x + this.tileSize * Math.cos(i * 2 * Math.PI / this.sides + Math.PI/6); 277 | y = this.y + this.tileSize * Math.sin(i * 2 * Math.PI / this.sides + Math.PI/6); 278 | this.vertices.push([x, y]); 279 | } 280 | } 281 | Polygon.prototype.rotateVertices = function() { 282 | // rotate all the vertices to achieve the overall rotational effect 283 | var rotation = fgRotation; 284 | rotation -= audioSource.volume > 10000 ? Math.sin(audioSource.volume/800000) : 0; 285 | for (var i = 0; i <= this.sides-1;i += 1) { 286 | this.vertices[i][0] = this.vertices[i][0] - this.vertices[i][1] * Math.sin(rotation); 287 | this.vertices[i][1] = this.vertices[i][1] + this.vertices[i][0] * Math.sin(rotation); 288 | } 289 | }; 290 | var minMental = 0, maxMental = 0; 291 | Polygon.prototype.calculateOffset = function(coords) { 292 | var angle = Math.atan(coords[1]/coords[0]); 293 | var distance = Math.sqrt(Math.pow(coords[0], 2) + Math.pow(coords[1], 2)); // a bit of pythagoras 294 | var mentalFactor = Math.min(Math.max((Math.tan(audioSource.volume/6000) * 0.5), -20), 2); // this factor makes the visualization go crazy wild 295 | /* 296 | // debug 297 | minMental = mentalFactor < minMental ? mentalFactor : minMental; 298 | maxMental = mentalFactor > maxMental ? mentalFactor : maxMental;*/ 299 | var offsetFactor = Math.pow(distance/3, 2) * (audioSource.volume/2000000) * (Math.pow(this.high, 1.3)/300) * mentalFactor; 300 | var offsetX = Math.cos(angle) * offsetFactor; 301 | var offsetY = Math.sin(angle) * offsetFactor; 302 | offsetX *= (coords[0] < 0) ? -1 : 1; 303 | offsetY *= (coords[0] < 0) ? -1 : 1; 304 | return [offsetX, offsetY]; 305 | }; 306 | Polygon.prototype.drawPolygon = function() { 307 | var bucket = Math.ceil(audioSource.streamData.length/tiles.length*this.num); 308 | var val = Math.pow((audioSource.streamData[bucket]/255),2)*255; 309 | //console.log(audioSource.streamData[bucket]/255); 310 | val *= this.num > 42 ? 1.1 : 1; 311 | // establish the value for this tile 312 | if (val > this.high) { 313 | this.high = val; 314 | } else { 315 | this.high -= this.decay; 316 | val = this.high; 317 | } 318 | 319 | // figure out what colour to fill it and then draw the polygon 320 | var r, g, b, a; 321 | if (val > 0) { 322 | this.ctx.beginPath(); 323 | var offset = this.calculateOffset(this.vertices[0]); 324 | this.ctx.moveTo(this.vertices[0][0] + offset[0], this.vertices[0][1] + offset[1]); 325 | // draw the polygon 326 | for (var i = 1; i <= this.sides-1;i += 1) { 327 | offset = this.calculateOffset(this.vertices[i]); 328 | this.ctx.lineTo (this.vertices[i][0] + offset[0], this.vertices[i][1] + offset[1]); 329 | } 330 | this.ctx.closePath(); 331 | 332 | if (val > 128) { 333 | r = (val-128)*2; 334 | g = ((Math.cos((2*val/128*Math.PI/2)- 4*Math.PI/3)+1)*128); 335 | b = (val-105)*3; 336 | } 337 | else if (val > 175) { 338 | r = (val-128)*2; 339 | g = 255; 340 | b = (val-105)*3; 341 | } 342 | else { 343 | r = ((Math.cos((2*val/128*Math.PI/2))+1)*128); 344 | g = ((Math.cos((2*val/128*Math.PI/2)- 4*Math.PI/3)+1)*128); 345 | b = ((Math.cos((2.4*val/128*Math.PI/2)- 2*Math.PI/3)+1)*128); 346 | } 347 | if (val > 210) { 348 | this.cubed = val; // add the cube effect if it's really loud 349 | } 350 | if (val > 120) { 351 | this.highlight = 100; // add the highlight effect if it's pretty loud 352 | } 353 | // set the alpha 354 | var e = 2.7182; 355 | a = (0.5/(1 + 40 * Math.pow(e, -val/8))) + (0.5/(1 + 40 * Math.pow(e, -val/20))); 356 | 357 | this.ctx.fillStyle = "rgba(" + 358 | Math.round(r) + ", " + 359 | Math.round(g) + ", " + 360 | Math.round(b) + ", " + 361 | a + ")"; 362 | // console.log(r); 363 | // console.log(g); 364 | // console.log(b); 365 | this.ctx.fill(); 366 | // stroke 367 | if (val > 20) { 368 | var strokeVal = 20; 369 | this.ctx.strokeStyle = "rgba(" + strokeVal + ", " + strokeVal + ", " + strokeVal + ", 0.5)"; 370 | this.ctx.lineWidth = 1; 371 | this.ctx.stroke(); 372 | } 373 | } 374 | // display the tile number for debug purposes 375 | /*this.ctx.font = "bold 12px sans-serif"; 376 | this.ctx.fillStyle = 'grey'; 377 | this.ctx.fillText(this.num, this.vertices[0][0], this.vertices[0][1]);*/ 378 | }; 379 | Polygon.prototype.drawHighlight = function() { 380 | this.ctx.beginPath(); 381 | // draw the highlight 382 | var offset = this.calculateOffset(this.vertices[0]); 383 | this.ctx.moveTo(this.vertices[0][0] + offset[0], this.vertices[0][1] + offset[1]); 384 | // draw the polygon 385 | for (var i = 0; i <= this.sides-1;i += 1) { 386 | offset = this.calculateOffset(this.vertices[i]); 387 | this.ctx.lineTo (this.vertices[i][0] + offset[0], this.vertices[i][1] + offset[1]); 388 | } 389 | this.ctx.closePath(); 390 | var a = this.highlight/100; 391 | this.ctx.strokeStyle = "rgba(255, 255, 255, " + a + ")"; 392 | this.ctx.lineWidth = 1; 393 | this.ctx.stroke(); 394 | this.highlight -= 0.5; 395 | }; 396 | 397 | var makePolygonArray = function() { 398 | tiles = []; 399 | /** 400 | * Arrange into a grid x, y, with the y axis at 60 degrees to the x, rather than 401 | * the usual 90. 402 | * @type {number} 403 | */ 404 | var i = 0; // unique number for each tile 405 | tiles.push(new Polygon(6, 0, 0, tileSize, fgCtx, i)); // the centre tile 406 | i++; 407 | for (var layer = 1; layer < 7; layer++) { 408 | tiles.push(new Polygon(6, 0, layer, tileSize, fgCtx, i)); i++; 409 | tiles.push(new Polygon(6, 0, -layer, tileSize, fgCtx, i)); i++; 410 | for(var x = 1; x < layer; x++) { 411 | tiles.push(new Polygon(6, x, -layer, tileSize, fgCtx, i)); i++; 412 | tiles.push(new Polygon(6, -x, layer, tileSize, fgCtx, i)); i++; 413 | tiles.push(new Polygon(6, x, layer-x, tileSize, fgCtx, i)); i++; 414 | tiles.push(new Polygon(6, -x, -layer+x, tileSize, fgCtx, i)); i++; 415 | } 416 | for(var y = -layer; y <= 0; y++) { 417 | tiles.push(new Polygon(6, layer, y, tileSize, fgCtx, i)); i++; 418 | tiles.push(new Polygon(6, -layer, -y, tileSize, fgCtx, i)); i++; 419 | } 420 | } 421 | }; 422 | 423 | 424 | function Star(x, y, starSize, ctx) { 425 | this.x = x; 426 | this.y = y; 427 | this.angle = Math.atan(Math.abs(y)/Math.abs(x)); 428 | this.starSize = starSize; 429 | this.ctx = ctx; 430 | this.high = 0; 431 | } 432 | Star.prototype.drawStar = function() { 433 | var distanceFromCentre = Math.sqrt(Math.pow(this.x, 2) + Math.pow(this.y, 2)); 434 | 435 | // stars as lines 436 | var brightness = 200 + Math.min(Math.round(this.high * 5), 55); 437 | this.ctx.lineWidth= 0.5 + distanceFromCentre/2000 * Math.max(this.starSize/2, 1); 438 | this.ctx.strokeStyle='rgba(' + brightness + ', ' + brightness + ', ' + brightness + ', 1)'; 439 | this.ctx.beginPath(); 440 | this.ctx.moveTo(this.x,this.y); 441 | var lengthFactor = 1 + Math.min(Math.pow(distanceFromCentre,2)/30000 * Math.pow(audioSource.volume, 2)/6000000, distanceFromCentre); 442 | var toX = Math.cos(this.angle) * -lengthFactor; 443 | var toY = Math.sin(this.angle) * -lengthFactor; 444 | toX *= this.x > 0 ? 1 : -1; 445 | toY *= this.y > 0 ? 1 : -1; 446 | this.ctx.lineTo(this.x + toX, this.y + toY); 447 | this.ctx.stroke(); 448 | this.ctx.closePath(); 449 | 450 | // starfield movement coming towards the camera 451 | var speed = lengthFactor/20 * this.starSize; 452 | this.high -= Math.max(this.high - 0.0001, 0); 453 | if (speed > this.high) { 454 | this.high = speed; 455 | } 456 | var dX = Math.cos(this.angle) * this.high; 457 | var dY = Math.sin(this.angle) * this.high; 458 | this.x += this.x > 0 ? dX : -dX; 459 | this.y += this.y > 0 ? dY : -dY; 460 | 461 | var limitY = fgCanvas.height/2 + 500; 462 | var limitX = fgCanvas.width/2 + 500; 463 | if ((this.y > limitY || this.y < -limitY) || (this.x > limitX || this.x < -limitX)) { 464 | // it has gone off the edge so respawn it somewhere near the middle. 465 | this.x = (Math.random() - 0.5) * fgCanvas.width/3; 466 | this.y = (Math.random() - 0.5) * fgCanvas.height/3; 467 | this.angle = Math.atan(Math.abs(this.y)/Math.abs(this.x)); 468 | } 469 | }; 470 | 471 | var makeStarArray = function() { 472 | var x, y, starSize; 473 | stars = []; 474 | var limit = fgCanvas.width / 15; // how many stars? 475 | for (var i = 0; i < limit; i ++) { 476 | x = (Math.random() - 0.5) * fgCanvas.width; 477 | y = (Math.random() - 0.5) * fgCanvas.height; 478 | starSize = (Math.random()+0.1)*3; 479 | stars.push(new Star(x, y, starSize, sfCtx)); 480 | } 481 | }; 482 | 483 | 484 | var drawBg = function() { 485 | bgCtx.clearRect(0, 0, bgCanvas.width, bgCanvas.height); 486 | var r, g, b, a; 487 | var val = audioSource.volume/1000; 488 | r = 200 + (Math.sin(val) + 1) * 28; 489 | g = val * 2; 490 | b = val * 8; 491 | a = Math.sin(val+3*Math.PI/2) + 1; 492 | bgCtx.beginPath(); 493 | bgCtx.rect(0, 0, bgCanvas.width, bgCanvas.height); 494 | // create radial gradient 495 | var grd = bgCtx.createRadialGradient(bgCanvas.width/2, bgCanvas.height/2, val, bgCanvas.width/2, bgCanvas.height/2, bgCanvas.width-Math.min(Math.pow(val, 2.7), bgCanvas.width - 20)); 496 | grd.addColorStop(0, 'rgba(0,0,0,0)');// centre is transparent black 497 | grd.addColorStop(0.8, "rgba(" + 498 | Math.round(r) + ", " + 499 | Math.round(g) + ", " + 500 | Math.round(b) + ", 0.4)"); // edges are reddish 501 | 502 | bgCtx.fillStyle = grd; 503 | bgCtx.fill(); 504 | 505 | /* 506 | // debug data 507 | bgCtx.font = "bold 30px sans-serif"; 508 | bgCtx.fillStyle = 'grey'; 509 | bgCtx.fillText("val: " + val, 30, 30); 510 | bgCtx.fillText("r: " + r , 30, 60); 511 | bgCtx.fillText("g: " + g , 30, 90); 512 | bgCtx.fillText("b: " + b , 30, 120); 513 | bgCtx.fillText("a: " + a , 30, 150);*/ 514 | }; 515 | 516 | this.resizeCanvas = function() { 517 | if (fgCanvas) { 518 | // resize the foreground canvas 519 | fgCanvas.width = window.innerWidth; 520 | fgCanvas.height = window.innerHeight; 521 | fgCtx.translate(fgCanvas.width/2,fgCanvas.height/2); 522 | 523 | // resize the bg canvas 524 | bgCanvas.width = window.innerWidth; 525 | bgCanvas.height = window.innerHeight; 526 | // resize the starfield canvas 527 | sfCanvas.width = window.innerWidth; 528 | sfCanvas.height = window.innerHeight; 529 | sfCtx.translate(fgCanvas.width/2,fgCanvas.height/2); 530 | 531 | tileSize = fgCanvas.width > fgCanvas.height ? fgCanvas.width / 25 : fgCanvas.height / 25; 532 | 533 | 534 | drawBg(); 535 | makePolygonArray(); 536 | makeStarArray() 537 | } 538 | }; 539 | 540 | var rotateForeground = function() { 541 | tiles.forEach(function(tile) { 542 | tile.rotateVertices(); 543 | }); 544 | }; 545 | 546 | var draw = function() { 547 | fgCtx.clearRect(-fgCanvas.width, -fgCanvas.height, fgCanvas.width*2, fgCanvas.height *2); 548 | sfCtx.clearRect(-fgCanvas.width/2, -fgCanvas.height/2, fgCanvas.width, fgCanvas.height); 549 | 550 | stars.forEach(function(star) { 551 | star.drawStar(); 552 | }); 553 | tiles.forEach(function(tile) { 554 | tile.drawPolygon(); 555 | }); 556 | tiles.forEach(function(tile) { 557 | if (tile.highlight > 0) { 558 | tile.drawHighlight(); 559 | } 560 | }); 561 | 562 | // debug 563 | /* fgCtx.font = "bold 24px sans-serif"; 564 | fgCtx.fillStyle = 'grey'; 565 | fgCtx.fillText("minMental:" + minMental, 10, 10); 566 | fgCtx.fillText("maxMental:" + maxMental, 10, 40);*/ 567 | requestAnimationFrame(draw); 568 | }; 569 | 570 | this.init = function(options) { 571 | audioSource = options.audioSource; 572 | var container = document.getElementById(options.containerId); 573 | 574 | // foreground hexagons layer 575 | fgCanvas = document.createElement('canvas'); 576 | fgCanvas.setAttribute('style', 'position: absolute; z-index: 10'); 577 | fgCtx = fgCanvas.getContext("2d"); 578 | container.appendChild(fgCanvas); 579 | 580 | // middle starfield layer 581 | sfCanvas = document.createElement('canvas'); 582 | sfCtx = sfCanvas.getContext("2d"); 583 | sfCanvas.setAttribute('style', 'position: absolute; z-index: 5'); 584 | container.appendChild(sfCanvas); 585 | 586 | // background image layer 587 | bgCanvas = document.createElement('canvas'); 588 | bgCtx = bgCanvas.getContext("2d"); 589 | container.appendChild(bgCanvas); 590 | 591 | makePolygonArray(); 592 | makeStarArray(); 593 | 594 | this.resizeCanvas(); 595 | draw(); 596 | 597 | 598 | setInterval(drawBg, 100); 599 | setInterval(rotateForeground, 20); 600 | // resize the canvas to fill browser window dynamically 601 | window.addEventListener('resize', this.resizeCanvas, false); 602 | }; 603 | }; 604 | 605 | /** 606 | * Makes a request to the Soundcloud API and returns the JSON data. 607 | */ 608 | var SoundcloudLoader = function(player,uiUpdater) { 609 | var self = this; 610 | var client_id = "YOUR_SOUNDCLOUD_CLIENT_ID"; // to get an ID go to http://developers.soundcloud.com/ 611 | this.sound = {}; 612 | this.streamUrl = ""; 613 | this.errorMessage = ""; 614 | this.player = player; 615 | this.uiUpdater = uiUpdater; 616 | 617 | /** 618 | * Loads the JSON stream data object from the URL of the track (as given in the location bar of the browser when browsing Soundcloud), 619 | * and on success it calls the callback passed to it (for example, used to then send the stream_url to the audiosource object). 620 | * @param track_url 621 | * @param callback 622 | */ 623 | this.loadStream = function(track_url, successCallback, errorCallback) { 624 | SC.initialize({ 625 | client_id: client_id 626 | }); 627 | SC.get('/resolve', { url: track_url }, function(sound) { 628 | if (sound.errors) { 629 | self.errorMessage = ""; 630 | for (var i = 0; i < sound.errors.length; i++) { 631 | self.errorMessage += sound.errors[i].error_message + '
'; 632 | } 633 | self.errorMessage += 'Make sure the URL has the correct format: https://soundcloud.com/user/title-of-the-track'; 634 | errorCallback(); 635 | } else { 636 | 637 | if(sound.kind=="playlist"){ 638 | self.sound = sound; 639 | self.streamPlaylistIndex = 0; 640 | self.streamUrl = function(){ 641 | return sound.tracks[self.streamPlaylistIndex].stream_url + '?client_id=' + client_id; 642 | }; 643 | successCallback(); 644 | }else{ 645 | self.sound = sound; 646 | self.streamUrl = function(){ return sound.stream_url + '?client_id=' + client_id; }; 647 | successCallback(); 648 | } 649 | } 650 | }); 651 | }; 652 | 653 | 654 | this.directStream = function(direction){ 655 | if(direction=='toggle'){ 656 | if (this.player.paused) { 657 | this.player.play(); 658 | } else { 659 | this.player.pause(); 660 | } 661 | } 662 | else if(this.sound.kind=="playlist"){ 663 | if(direction=='coasting') { 664 | this.streamPlaylistIndex++; 665 | }else if(direction=='forward') { 666 | if(this.streamPlaylistIndex>=this.sound.track_count-1) this.streamPlaylistIndex = 0; 667 | else this.streamPlaylistIndex++; 668 | }else{ 669 | if(this.streamPlaylistIndex<=0) this.streamPlaylistIndex = this.sound.track_count-1; 670 | else this.streamPlaylistIndex--; 671 | } 672 | if(this.streamPlaylistIndex>=0 && this.streamPlaylistIndex<=this.sound.track_count-1) { 673 | this.player.setAttribute('src',this.streamUrl()); 674 | this.uiUpdater.update(this); 675 | this.player.play(); 676 | } 677 | } 678 | } 679 | 680 | 681 | }; 682 | 683 | /** 684 | * Class to update the UI when a new sound is loaded 685 | * @constructor 686 | */ 687 | var UiUpdater = function() { 688 | var controlPanel = document.getElementById('controlPanel'); 689 | var trackInfoPanel = document.getElementById('trackInfoPanel'); 690 | var infoImage = document.getElementById('infoImage'); 691 | var infoArtist = document.getElementById('infoArtist'); 692 | var infoTrack = document.getElementById('infoTrack'); 693 | var messageBox = document.getElementById('messageBox'); 694 | 695 | this.clearInfoPanel = function() { 696 | // first clear the current contents 697 | infoArtist.innerHTML = ""; 698 | infoTrack.innerHTML = ""; 699 | trackInfoPanel.className = 'hidden'; 700 | }; 701 | this.update = function(loader) { 702 | // update the track and artist into in the controlPanel 703 | var artistLink = document.createElement('a'); 704 | artistLink.setAttribute('href', loader.sound.user.permalink_url); 705 | artistLink.innerHTML = loader.sound.user.username; 706 | var trackLink = document.createElement('a'); 707 | trackLink.setAttribute('href', loader.sound.permalink_url); 708 | 709 | if(loader.sound.kind=="playlist"){ 710 | trackLink.innerHTML = "

" + loader.sound.tracks[loader.streamPlaylistIndex].title + "

" + "

"+loader.sound.title+"

"; 711 | }else{ 712 | trackLink.innerHTML = loader.sound.title; 713 | } 714 | 715 | var image = loader.sound.artwork_url ? loader.sound.artwork_url : loader.sound.user.avatar_url; // if no track artwork exists, use the user's avatar. 716 | infoImage.setAttribute('src', image); 717 | 718 | infoArtist.innerHTML = ''; 719 | infoArtist.appendChild(artistLink); 720 | 721 | infoTrack.innerHTML = ''; 722 | infoTrack.appendChild(trackLink); 723 | 724 | // display the track info panel 725 | trackInfoPanel.className = ''; 726 | 727 | // add a hash to the URL so it can be shared or saved 728 | var trackToken = loader.sound.permalink_url.substr(22); 729 | window.location = '#' + trackToken; 730 | }; 731 | this.toggleControlPanel = function() { 732 | if (controlPanel.className.indexOf('hidden') === 0) { 733 | controlPanel.className = ''; 734 | } else { 735 | controlPanel.className = 'hidden'; 736 | } 737 | }; 738 | this.displayMessage = function(title, message) { 739 | messageBox.innerHTML = ''; // reset the contents 740 | 741 | var titleElement = document.createElement('h3'); 742 | titleElement.innerHTML = title; 743 | 744 | var messageElement = document.createElement('p'); 745 | messageElement.innerHTML = message; 746 | 747 | var closeButton = document.createElement('a'); 748 | closeButton.setAttribute('href', '#'); 749 | closeButton.innerHTML = 'close'; 750 | closeButton.addEventListener('click', function(e) { 751 | e.preventDefault(); 752 | messageBox.className = 'hidden'; 753 | }); 754 | 755 | messageBox.className = ''; 756 | // stick them into the container div 757 | messageBox.appendChild(titleElement); 758 | messageBox.appendChild(messageElement); 759 | messageBox.appendChild(closeButton); 760 | }; 761 | }; 762 | 763 | window.onload = function init() { 764 | 765 | var visualizer = new Visualizer(); 766 | var player = document.getElementById('player'); 767 | var uiUpdater = new UiUpdater(); 768 | var loader = new SoundcloudLoader(player,uiUpdater); 769 | 770 | 771 | var audioSource = new MicrophoneAudioSource(player); 772 | // var audioSource = new SoundCloudAudioSource(player); 773 | var form = document.getElementById('form'); 774 | var loadAndUpdate = function(trackUrl) { 775 | loader.loadStream(trackUrl, 776 | function() { 777 | uiUpdater.clearInfoPanel(); 778 | audioSource.playStream(loader.streamUrl()); 779 | uiUpdater.update(loader); 780 | setTimeout(uiUpdater.toggleControlPanel, 3000); // auto-hide the control panel 781 | }, 782 | function() { 783 | uiUpdater.displayMessage("Error", loader.errorMessage); 784 | }); 785 | }; 786 | 787 | visualizer.init({ 788 | containerId: 'visualizer', 789 | audioSource: audioSource 790 | }); 791 | 792 | 793 | uiUpdater.toggleControlPanel(); 794 | // on load, check to see if there is a track token in the URL, and if so, load that automatically 795 | if (window.location.hash) { 796 | var trackUrl = 'https://soundcloud.com/' + window.location.hash.substr(1); 797 | loadAndUpdate(trackUrl); 798 | } 799 | 800 | // handle the form submit event to load the new URL 801 | form.addEventListener('submit', function(e) { 802 | e.preventDefault(); 803 | var trackUrl = document.getElementById('input').value; 804 | loadAndUpdate(trackUrl); 805 | }); 806 | var toggleButton = document.getElementById('toggleButton') 807 | toggleButton.addEventListener('click', function(e) { 808 | e.preventDefault(); 809 | uiUpdater.toggleControlPanel(); 810 | }); 811 | var aboutButton = document.getElementById('credit'); 812 | aboutButton.addEventListener('click', function(e) { 813 | e.preventDefault(); 814 | var message = document.getElementById('info').innerHTML; 815 | uiUpdater.displayMessage("About", message); 816 | }); 817 | 818 | window.addEventListener("keydown", keyControls, false); 819 | 820 | function keyControls(e) { 821 | switch(e.keyCode) { 822 | case 32: 823 | // spacebar pressed 824 | loader.directStream('toggle'); 825 | break; 826 | case 37: 827 | // left key pressed 828 | loader.directStream('backward'); 829 | break; 830 | case 39: 831 | // right key pressed 832 | loader.directStream('forward'); 833 | break; 834 | } 835 | } 836 | 837 | 838 | }; -------------------------------------------------------------------------------- /tests/spacebg.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GabrielchenCN/yunba-wifi-bulb-example/1be46265c524f8b299a2890920402e0d391569a9/tests/spacebg.jpg -------------------------------------------------------------------------------- /tests/test01_tiles.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 59 | 64 | 65 | 66 | 67 | 68 | -------------------------------------------------------------------------------- /tests/test02_hexagons.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 100 | 109 | 110 | 111 | 112 | 113 | -------------------------------------------------------------------------------- /tests/test03_cubes.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 125 | 134 | 135 | 136 | 137 | 138 | -------------------------------------------------------------------------------- /tests/test03_cubes_optimized.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 126 | 135 | 136 | 137 | 138 | 139 | -------------------------------------------------------------------------------- /tests/test04_audio_analyzer.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 45 | 50 | 51 | 52 | 53 | 54 | -------------------------------------------------------------------------------- /tests/test05_audio_hexagons.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 253 | 262 | 263 | 264 | 265 | 266 | -------------------------------------------------------------------------------- /tests/test05a_colour_scale.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 45 | 46 | 47 | 48 | 49 | -------------------------------------------------------------------------------- /tests/test07_audio_hexagons_plus.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 353 | 376 | 377 | 378 | 379 | 380 | 381 | -------------------------------------------------------------------------------- /tests/test08_audio_hexagons_centered.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 413 | 436 | 437 | 438 | 439 | 440 | 441 | -------------------------------------------------------------------------------- /tests/test09_audio_hexagons_starfield.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 436 | 466 | 467 | 468 | 469 | 470 | 471 | 472 | -------------------------------------------------------------------------------- /tests/test10_audio_hexagons_starfield_2.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 391 | 421 | 422 | 423 | 424 | 425 | 426 | 427 | -------------------------------------------------------------------------------- /tests/test11_embedded_audio.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 33 | 39 | 40 | 41 | 42 |
43 | 44 | 45 | -------------------------------------------------------------------------------- /tests/test12_soundcloud_visualizer.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 463 | 477 | 478 | 479 |
480 |
481 | 482 |
483 | 484 |
485 | 486 | -------------------------------------------------------------------------------- /webapi.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Web Audio API 学习 8 | 76 | 81 | 82 | 83 |

从audio源获取声音

84 | 85 |

audio读取声音

86 | 87 |

频域图模仿

88 | 89 |

圆形声波图

90 | 91 | 92 | 144 | 145 | --------------------------------------------------------------------------------