From 5e1d6fe6d0509657665b4e6338ec47b06001ccaf Mon Sep 17 00:00:00 2001 From: samuelmaddock Date: Sat, 12 Jul 2014 20:03:59 -0400 Subject: [PATCH] Initial commit --- .editorconfig | 18 + .gitignore | 6 + LICENSE | 21 + README.md | 12 + addon.txt | 10 + content/materials/theater/STATIC.vmt | 15 + content/materials/theater/STATIC.vtf | Bin 0 -> 349744 bytes html/.gitattributes | 1 + html/.gitignore | 6 + html/.jshintrc | 21 + html/Gruntfile.js | 406 +++++++++++ html/app/favicon.ico | Bin 0 -> 4286 bytes html/app/index.html | 24 + html/app/scripts/main.js | 16 + html/app/styles/main.scss | 17 + html/app/vimeo.html | 95 +++ html/app/vimeo_playground.html | 642 ++++++++++++++++++ html/bower.json | 8 + html/package.json | 33 + html/test/.bowerrc | 3 + html/test/bower.json | 9 + html/test/index.html | 27 + html/test/spec/test.js | 13 + lua/autorun/includes/extensions/cl_draw.lua | 76 +++ lua/autorun/includes/extensions/sh_file.lua | 10 + lua/autorun/includes/extensions/sh_math.lua | 9 + lua/autorun/includes/extensions/sh_table.lua | 21 + lua/autorun/includes/extensions/sh_url.lua | 452 ++++++++++++ lua/autorun/includes/modules/EventEmitter.lua | 179 +++++ lua/autorun/includes/modules/browserpool.lua | 299 ++++++++ lua/autorun/includes/modules/control.lua | 96 +++ lua/autorun/includes/modules/htmlmaterial.lua | 140 ++++ lua/autorun/mediaplayer.lua | 81 +++ lua/autorun/mediaplayer/cl_cvars.lua | 3 + lua/autorun/mediaplayer/cl_idlescreen.lua | 55 ++ lua/autorun/mediaplayer/cl_init.lua | 254 +++++++ lua/autorun/mediaplayer/config/server.lua | 28 + .../mediaplayer/controls/dhtmlcontrols.lua | 349 ++++++++++ .../mediaplayer/controls/dmediaplayerhtml.lua | 390 +++++++++++ .../controls/dmediaplayerrequest.lua | 128 ++++ lua/autorun/mediaplayer/init.lua | 160 +++++ .../mediaplayer/players/_mixins/vote.lua | 15 + .../mediaplayer/players/base/cl_draw.lua | 171 +++++ .../players/base/cl_fullscreen.lua | 78 +++ .../mediaplayer/players/base/cl_init.lua | 140 ++++ .../mediaplayer/players/base/cl_net.lua | 42 ++ lua/autorun/mediaplayer/players/base/init.lua | 490 +++++++++++++ .../mediaplayer/players/base/shared.lua | 367 ++++++++++ .../mediaplayer/players/base/sv_net.lua | 27 + .../mediaplayer/players/entity/cl_init.lua | 106 +++ .../mediaplayer/players/entity/init.lua | 10 + .../mediaplayer/players/entity/sh_meta.lua | 85 +++ .../mediaplayer/players/entity/shared.lua | 89 +++ .../services/audiofile/cl_init.lua | 297 ++++++++ .../mediaplayer/services/audiofile/init.lua | 112 +++ .../mediaplayer/services/audiofile/shared.lua | 22 + .../mediaplayer/services/base/cl_init.lua | 33 + .../mediaplayer/services/base/init.lua | 102 +++ .../mediaplayer/services/base/shared.lua | 191 ++++++ lua/autorun/mediaplayer/services/browser.lua | 130 ++++ .../services/googledrive/cl_init.lua | 31 + .../mediaplayer/services/googledrive/init.lua | 87 +++ .../services/googledrive/shared.lua | 58 ++ .../mediaplayer/services/html5_video.lua | 57 ++ lua/autorun/mediaplayer/services/image.lua | 23 + lua/autorun/mediaplayer/services/jwplayer.lua | 37 + .../mediaplayer/services/resource/cl_init.lua | 16 + .../mediaplayer/services/resource/init.lua | 35 + .../mediaplayer/services/resource/shared.lua | 21 + .../mediaplayer/services/shoutcast.lua | 15 + .../services/soundcloud/cl_init.lua | 1 + .../mediaplayer/services/soundcloud/init.lua | 104 +++ .../services/soundcloud/shared.lua | 20 + .../mediaplayer/services/twitch/cl_init.lua | 56 ++ .../mediaplayer/services/twitch/init.lua | 92 +++ .../mediaplayer/services/twitch/shared.lua | 54 ++ .../services/twitchstream/cl_init.lua | 23 + .../services/twitchstream/init.lua | 61 ++ .../services/twitchstream/shared.lua | 44 ++ .../mediaplayer/services/vimeo/cl_init.lua | 49 ++ .../mediaplayer/services/vimeo/init.lua | 68 ++ .../mediaplayer/services/vimeo/shared.lua | 38 ++ .../mediaplayer/services/youtube/cl_init.lua | 179 +++++ .../mediaplayer/services/youtube/init.lua | 149 ++++ .../mediaplayer/services/youtube/shared.lua | 79 +++ lua/autorun/mediaplayer/sh_history.lua | 105 +++ lua/autorun/mediaplayer/sh_mediaplayer.lua | 198 ++++++ lua/autorun/mediaplayer/sh_services.lua | 127 ++++ lua/autorun/mediaplayer/shared.lua | 78 +++ lua/autorun/mediaplayer/sv_metadata.lua | 174 +++++ lua/autorun/menubar/mp_options.lua | 8 + lua/autorun/properties/mediaplayer.lua | 185 +++++ lua/entities/mediaplayer_base/shared.lua | 84 +++ lua/entities/mediaplayer_example/shared.lua | 20 + lua/entities/mediaplayer_projector/shared.lua | 86 +++ mediaplayer.sublime-project | 8 + resource/fonts/ClearSans-Medium.ttf | Bin 0 -> 70736 bytes 97 files changed, 9110 insertions(+) create mode 100644 .editorconfig create mode 100644 .gitignore create mode 100644 LICENSE create mode 100644 README.md create mode 100644 addon.txt create mode 100644 content/materials/theater/STATIC.vmt create mode 100644 content/materials/theater/STATIC.vtf create mode 100644 html/.gitattributes create mode 100644 html/.gitignore create mode 100644 html/.jshintrc create mode 100644 html/Gruntfile.js create mode 100644 html/app/favicon.ico create mode 100644 html/app/index.html create mode 100644 html/app/scripts/main.js create mode 100644 html/app/styles/main.scss create mode 100644 html/app/vimeo.html create mode 100644 html/app/vimeo_playground.html create mode 100644 html/bower.json create mode 100644 html/package.json create mode 100644 html/test/.bowerrc create mode 100644 html/test/bower.json create mode 100644 html/test/index.html create mode 100644 html/test/spec/test.js create mode 100644 lua/autorun/includes/extensions/cl_draw.lua create mode 100644 lua/autorun/includes/extensions/sh_file.lua create mode 100644 lua/autorun/includes/extensions/sh_math.lua create mode 100644 lua/autorun/includes/extensions/sh_table.lua create mode 100644 lua/autorun/includes/extensions/sh_url.lua create mode 100644 lua/autorun/includes/modules/EventEmitter.lua create mode 100644 lua/autorun/includes/modules/browserpool.lua create mode 100644 lua/autorun/includes/modules/control.lua create mode 100644 lua/autorun/includes/modules/htmlmaterial.lua create mode 100644 lua/autorun/mediaplayer.lua create mode 100644 lua/autorun/mediaplayer/cl_cvars.lua create mode 100644 lua/autorun/mediaplayer/cl_idlescreen.lua create mode 100644 lua/autorun/mediaplayer/cl_init.lua create mode 100644 lua/autorun/mediaplayer/config/server.lua create mode 100644 lua/autorun/mediaplayer/controls/dhtmlcontrols.lua create mode 100644 lua/autorun/mediaplayer/controls/dmediaplayerhtml.lua create mode 100644 lua/autorun/mediaplayer/controls/dmediaplayerrequest.lua create mode 100644 lua/autorun/mediaplayer/init.lua create mode 100644 lua/autorun/mediaplayer/players/_mixins/vote.lua create mode 100644 lua/autorun/mediaplayer/players/base/cl_draw.lua create mode 100644 lua/autorun/mediaplayer/players/base/cl_fullscreen.lua create mode 100644 lua/autorun/mediaplayer/players/base/cl_init.lua create mode 100644 lua/autorun/mediaplayer/players/base/cl_net.lua create mode 100644 lua/autorun/mediaplayer/players/base/init.lua create mode 100644 lua/autorun/mediaplayer/players/base/shared.lua create mode 100644 lua/autorun/mediaplayer/players/base/sv_net.lua create mode 100644 lua/autorun/mediaplayer/players/entity/cl_init.lua create mode 100644 lua/autorun/mediaplayer/players/entity/init.lua create mode 100644 lua/autorun/mediaplayer/players/entity/sh_meta.lua create mode 100644 lua/autorun/mediaplayer/players/entity/shared.lua create mode 100644 lua/autorun/mediaplayer/services/audiofile/cl_init.lua create mode 100644 lua/autorun/mediaplayer/services/audiofile/init.lua create mode 100644 lua/autorun/mediaplayer/services/audiofile/shared.lua create mode 100644 lua/autorun/mediaplayer/services/base/cl_init.lua create mode 100644 lua/autorun/mediaplayer/services/base/init.lua create mode 100644 lua/autorun/mediaplayer/services/base/shared.lua create mode 100644 lua/autorun/mediaplayer/services/browser.lua create mode 100644 lua/autorun/mediaplayer/services/googledrive/cl_init.lua create mode 100644 lua/autorun/mediaplayer/services/googledrive/init.lua create mode 100644 lua/autorun/mediaplayer/services/googledrive/shared.lua create mode 100644 lua/autorun/mediaplayer/services/html5_video.lua create mode 100644 lua/autorun/mediaplayer/services/image.lua create mode 100644 lua/autorun/mediaplayer/services/jwplayer.lua create mode 100644 lua/autorun/mediaplayer/services/resource/cl_init.lua create mode 100644 lua/autorun/mediaplayer/services/resource/init.lua create mode 100644 lua/autorun/mediaplayer/services/resource/shared.lua create mode 100644 lua/autorun/mediaplayer/services/shoutcast.lua create mode 100644 lua/autorun/mediaplayer/services/soundcloud/cl_init.lua create mode 100644 lua/autorun/mediaplayer/services/soundcloud/init.lua create mode 100644 lua/autorun/mediaplayer/services/soundcloud/shared.lua create mode 100644 lua/autorun/mediaplayer/services/twitch/cl_init.lua create mode 100644 lua/autorun/mediaplayer/services/twitch/init.lua create mode 100644 lua/autorun/mediaplayer/services/twitch/shared.lua create mode 100644 lua/autorun/mediaplayer/services/twitchstream/cl_init.lua create mode 100644 lua/autorun/mediaplayer/services/twitchstream/init.lua create mode 100644 lua/autorun/mediaplayer/services/twitchstream/shared.lua create mode 100644 lua/autorun/mediaplayer/services/vimeo/cl_init.lua create mode 100644 lua/autorun/mediaplayer/services/vimeo/init.lua create mode 100644 lua/autorun/mediaplayer/services/vimeo/shared.lua create mode 100644 lua/autorun/mediaplayer/services/youtube/cl_init.lua create mode 100644 lua/autorun/mediaplayer/services/youtube/init.lua create mode 100644 lua/autorun/mediaplayer/services/youtube/shared.lua create mode 100644 lua/autorun/mediaplayer/sh_history.lua create mode 100644 lua/autorun/mediaplayer/sh_mediaplayer.lua create mode 100644 lua/autorun/mediaplayer/sh_services.lua create mode 100644 lua/autorun/mediaplayer/shared.lua create mode 100644 lua/autorun/mediaplayer/sv_metadata.lua create mode 100644 lua/autorun/menubar/mp_options.lua create mode 100644 lua/autorun/properties/mediaplayer.lua create mode 100644 lua/entities/mediaplayer_base/shared.lua create mode 100644 lua/entities/mediaplayer_example/shared.lua create mode 100644 lua/entities/mediaplayer_projector/shared.lua create mode 100644 mediaplayer.sublime-project create mode 100644 resource/fonts/ClearSans-Medium.ttf diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..e7e7d05 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,18 @@ +# EditorConfig helps developers define and maintain consistent +# coding styles between different editors and IDEs +# editorconfig.org + +root = true + +[*] +end_of_line = crlf +charset = utf-8 +trim_trailing_whitespace = true +insert_final_newline = true + +[*.lua] +indent_style = tab +indent_size = 4 + +[*.md] +trim_trailing_whitespace = false diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..35e3fcf --- /dev/null +++ b/.gitignore @@ -0,0 +1,6 @@ +# general +*.todo +*.sublime-workspace + +# old mediaplayer service code +lua/autorun/mediaplayer/services/_deprecated diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..744a835 --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +The MIT License + +Copyright © 2014 GMod Media Player authors + +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: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +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. diff --git a/README.md b/README.md new file mode 100644 index 0000000..ec70055 --- /dev/null +++ b/README.md @@ -0,0 +1,12 @@ +Media Player +============ + +Media Player is an addon for Garry's Mod which features several media streaming services able to be played synchronously in multiplayer. It is currently in alpha and will be uploaded to Steam Workshop when it's closer to completion. + +### Installation ### + +Place the contents of this GitHub repository into a new addon folder within your `garrysmod/addons/` directory. For those unfamiliar with Git, press the `Download ZIP` button in the right-hand sidebar. + +### Notes ### + +Please do not distribute this content elsewhere such as uploading to Steam Workshop. It will be uploaded when it's finished. diff --git a/addon.txt b/addon.txt new file mode 100644 index 0000000..90e97d8 --- /dev/null +++ b/addon.txt @@ -0,0 +1,10 @@ +"AddonInfo" +{ + "name" "Media Player" + "version" "0.0.1" + "author_name" "Samuel Maddock" + "author_email" "contact@pixeltailgames.com" + "author_url" "http://github.com/pixeltailgames" + "info" "http://github.com/pixeltailgames/gm-mediaplayer" + "override" "0" +} diff --git a/content/materials/theater/STATIC.vmt b/content/materials/theater/STATIC.vmt new file mode 100644 index 0000000..3fbf397 --- /dev/null +++ b/content/materials/theater/STATIC.vmt @@ -0,0 +1,15 @@ +"UnlitGeneric" +{ + "$basetexture" "theater/STATIC" + "$surfaceprop" "glass" + "%keywords" "theater" + "Proxies" + { + "AnimatedTexture" + { + "animatedTextureVar" "$basetexture" + "animatedTextureFrameNumVar" "$frame" + "animatedTextureFrameRate" "16" + } + } +} diff --git a/content/materials/theater/STATIC.vtf b/content/materials/theater/STATIC.vtf new file mode 100644 index 0000000000000000000000000000000000000000..5a47d5699349f311dba95f770d375f202f867e38 GIT binary patch literal 349744 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≷;FouctXnLv^{f!m8*rH(;n=5 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

*rLMsGO~X5M06m@JzWCV;mU^A%>+DOVRp%!? z=t;f@y7rr=bRMn^R_PwKmKZdtd0>vI&b_96TK*Jyqkgo2XM>-Bc?W+$-12YZMhoJnr9ea*-5HP@LNT+it`>efH_JbWo`k9cs#a!Z_d`pZst z!lRdm4+7j2`U3bpIIw(hL$3e)OPqt@$XV|O`RzD_%s>41Ke zy{kcdPoIMe7|M^~FMJ&AJ@(%@SNI9L%3;~9RXjrFvGUj-eV_SnVGj&j~NLf?l95Pa#MCLZbPf&;#@vO*WPE+^dBZIl(nWm=`5&w=aqjch z(Ulb!9CEa@?JMWVE37ART#{PQ%fyrZyElK@?_Cf-McnxJ9nc8 z#v>(gb?dAEFAnaLxfwUSGvuZ2K4m)i+@@Nc__3LH$UrURCAb!vs9rk3-_gUGBTx*D z;6ET2E6im_{>sfuw(4Z}KYZsoCnrrc^4xEusY0apGxGW8XVSgDp}%5V_&hvG-`R|s z=Ka3ew5{3nnzsj|^D_hBJHhMK{R%rlUcLf;2tS=RiA*!{ypl4m_<*LjRk8PypK0?l zdMG)O)#sxftFCX)ujP8?n;#>eXASqppZo2m!2t7RkxOt|y$A6pz!$(bcwZy@OPRyS zL6QWi?zb{Y$0zn{Y76IsJb8utKSLZeR#yb4{Z7=$jkz~o{tM{^-taxLKJa6% zILa5fxt>A!Q439O?mOVqvt9l6eVD~L2KPwsm`^`755dDpJCG;dH}HX3;bVX=-CDp; zxI&$Jg8i9arU4g!6Z%-qKMKa1ZE)su@J6}V@w}CY)sFs<_$@%;LjEDEY;#kyTmDDtJ>X>* zrDqDK$>?zZn_FD`UFN&281v{9^?`(VM&Jiyu+dBSn@NBx_&L@nZmaZG>X+`+b?gLv z1L}i|)<@i{H^@(tN$pWz0r)NEU;Jr9p<=_!byvew$OFee7rgwi>3V?tQG)xZp0Ucp z{yFEG@zhoIvuAVQLzu(ml+5LHhod_sYN^wdEAuGnci{k%drY;zE~ya-O&AG z;30gR#*~}T`uA0w6?n|R$KIKXdbKN?R6LSMx(ja}_>0Y7PL@iWKHz$bwk>>N$0-Be zY>O2}j)hyw*Wi^PpEi8s4nNKP6g=3!U`M}M6wY&aFmq!ZJNWSZF3$zthruPx_YQvX zKa3QxXVLkB`o8ble9unvE5+dzh>3)s8$%zR19vgIC_ET>Z}@OHNGS}+|mr5NOIoeV4v3! zPgDOyuHz*G4-s|b7?Z1C%|~aBnx*UHABV1)vE#Cjyr1wzV>`;u-6Onr4>&;dR$$yM z$xq1j+tju174EjCdL_8QY!Q2on!r!-=YLJK@Z7P8S5q95>VVOmcyfB)EbXN(Ejwl2 zMjoviJdo?Gg`>7V_w&R?H?KM#41ENKhS6uy!mg1P^m*{ho9Ri#&(^p(_ThhhVe>qNzF)i=e6U0!wOr)fkJ<2d zFt6sEDbqIy(-1d}|JtBl|rBZv*(8;4M2FfCn=g zVBhz!->I~j);V6zm>K%tH~-{sdWXQPSiAnoI}X2dumEn6JdALW&i{IL4j!M(lI&Jf z!X5s_tN+jlZ6S46CXLg8uX3NA$~Q$v|TFlJGbv?|zkp^_$e?a1YYT_doK>YH`yCuSRqDm)vhths~Tc z^kAT1(m%!T<2o7nC!BXrbvw-X^cOFK8%&!q`QwB&qk1pS(=VSv4pKA>3tuolVAfRs ztf%lLJ#@-7b`##UW|WtkT$6no49*OcOFFmpUac_P_I30XQ$BT_eesN4P~I!- z8RP>Vjk1|iAAACy6nHE)Pq<|ZJkI7(d+HJ9wKe2x=_@+lt!^z)W`gA+32e@fMO}AP z@rSkKQiq23Y1*!+FCScP(Htn$l0@VT((pr6)nUbH>iFzt)(qn&RCfvBpr%9or5c�XZ-Q@Y9)$7|ze=qJE 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 literal 0 HcmV?d00001 diff --git a/html/.gitattributes b/html/.gitattributes new file mode 100644 index 0000000..2125666 --- /dev/null +++ b/html/.gitattributes @@ -0,0 +1 @@ +* text=auto \ No newline at end of file diff --git a/html/.gitignore b/html/.gitignore new file mode 100644 index 0000000..2f4628a --- /dev/null +++ b/html/.gitignore @@ -0,0 +1,6 @@ +node_modules +dist +.tmp +.sass-cache +bower_components +test/bower_components diff --git a/html/.jshintrc b/html/.jshintrc new file mode 100644 index 0000000..287bc43 --- /dev/null +++ b/html/.jshintrc @@ -0,0 +1,21 @@ +{ + "node": true, + "browser": true, + "esnext": true, + "bitwise": true, + "camelcase": true, + "curly": true, + "eqeqeq": true, + "immed": true, + "indent": 4, + "latedef": true, + "newcap": true, + "noarg": true, + "quotmark": "single", + "undef": true, + "unused": true, + "strict": true, + "trailing": true, + "smarttabs": true, + "jquery": true +} diff --git a/html/Gruntfile.js b/html/Gruntfile.js new file mode 100644 index 0000000..c9241b5 --- /dev/null +++ b/html/Gruntfile.js @@ -0,0 +1,406 @@ +// Generated on 2014-06-15 using generator-webapp 0.4.9 +'use strict'; + +// # Globbing +// for performance reasons we're only matching one level down: +// 'test/spec/{,*/}*.js' +// use this if you want to recursively match all subfolders: +// 'test/spec/**/*.js' + +module.exports = function (grunt) { + + // Load grunt tasks automatically + require('load-grunt-tasks')(grunt); + + // Time how long tasks take. Can help when optimizing build times + require('time-grunt')(grunt); + + // Configurable paths + var config = { + app: 'app', + dist: 'dist' + }; + + // Define the configuration for all the tasks + grunt.initConfig({ + + // Project settings + config: config, + + // Watches files for changes and runs tasks based on the changed files + watch: { + bower: { + files: ['bower.json'], + tasks: ['bowerInstall'] + }, + js: { + files: ['<%= config.app %>/scripts/{,*/}*.js'], + tasks: ['jshint'], + options: { + livereload: true + } + }, + jstest: { + files: ['test/spec/{,*/}*.js'], + tasks: ['test:watch'] + }, + gruntfile: { + files: ['Gruntfile.js'] + }, + sass: { + files: ['<%= config.app %>/styles/{,*/}*.{scss,sass}'], + tasks: ['sass:server', 'autoprefixer'] + }, + styles: { + files: ['<%= config.app %>/styles/{,*/}*.css'], + tasks: ['newer:copy:styles', 'autoprefixer'] + }, + livereload: { + options: { + livereload: '<%= connect.options.livereload %>' + }, + files: [ + '<%= config.app %>/{,*/}*.html', + '.tmp/styles/{,*/}*.css', + '<%= config.app %>/images/{,*/}*' + ] + } + }, + + // The actual grunt server settings + connect: { + options: { + port: 80, + open: true, + livereload: 35729, + // Change this to '0.0.0.0' to access the server from outside + hostname: 'localhost' + }, + livereload: { + options: { + middleware: function(connect) { + return [ + connect.static('.tmp'), + connect().use('/bower_components', connect.static('./bower_components')), + connect.static(config.app) + ]; + } + } + }, + test: { + options: { + open: false, + port: 9001, + middleware: function(connect) { + return [ + connect.static('.tmp'), + connect.static('test'), + connect().use('/bower_components', connect.static('./bower_components')), + connect.static(config.app) + ]; + } + } + }, + dist: { + options: { + base: '<%= config.dist %>', + livereload: false + } + } + }, + + // Empties folders to start fresh + clean: { + dist: { + files: [{ + dot: true, + src: [ + '.tmp', + '<%= config.dist %>/*', + '!<%= config.dist %>/.git*' + ] + }] + }, + server: '.tmp' + }, + + // Make sure code styles are up to par and there are no obvious mistakes + jshint: { + options: { + jshintrc: '.jshintrc', + reporter: require('jshint-stylish') + }, + all: [ + 'Gruntfile.js', + '<%= config.app %>/scripts/{,*/}*.js', + '!<%= config.app %>/scripts/vendor/*', + 'test/spec/{,*/}*.js' + ] + }, + + // Mocha testing framework configuration options + mocha: { + all: { + options: { + run: true, + urls: ['http://<%= connect.test.options.hostname %>:<%= connect.test.options.port %>/index.html'] + } + } + }, + + // Compiles Sass to CSS and generates necessary files if requested + sass: { + options: { + loadPath: [ + 'bower_components' + ] + }, + dist: { + files: [{ + expand: true, + cwd: '<%= config.app %>/styles', + src: ['*.scss'], + dest: '.tmp/styles', + ext: '.css' + }] + }, + server: { + files: [{ + expand: true, + cwd: '<%= config.app %>/styles', + src: ['*.scss'], + dest: '.tmp/styles', + ext: '.css' + }] + } + }, + + // Add vendor prefixed styles + autoprefixer: { + options: { + browsers: ['last 1 version'] + }, + dist: { + files: [{ + expand: true, + cwd: '.tmp/styles/', + src: '{,*/}*.css', + dest: '.tmp/styles/' + }] + } + }, + + // Automatically inject Bower components into the HTML file + bowerInstall: { + app: { + src: ['<%= config.app %>/index.html'], + exclude: ['bower_components/bootstrap-sass-official/vendor/assets/javascripts/bootstrap.js'] + }, + sass: { + src: ['<%= config.app %>/styles/{,*/}*.{scss,sass}'] + } + }, + + // Renames files for browser caching purposes + rev: { + dist: { + files: { + src: [ + '<%= config.dist %>/scripts/{,*/}*.js', + '<%= config.dist %>/styles/{,*/}*.css', + '<%= config.dist %>/images/{,*/}*.*', + '<%= config.dist %>/styles/fonts/{,*/}*.*', + '<%= config.dist %>/*.{ico,png}' + ] + } + } + }, + + // Reads HTML for usemin blocks to enable smart builds that automatically + // concat, minify and revision files. Creates configurations in memory so + // additional tasks can operate on them + useminPrepare: { + options: { + dest: '<%= config.dist %>' + }, + html: '<%= config.app %>/index.html' + }, + + // Performs rewrites based on rev and the useminPrepare configuration + usemin: { + options: { + assetsDirs: ['<%= config.dist %>', '<%= config.dist %>/images'] + }, + html: ['<%= config.dist %>/{,*/}*.html'], + css: ['<%= config.dist %>/styles/{,*/}*.css'] + }, + + // The following *-min tasks produce minified files in the dist folder + imagemin: { + dist: { + files: [{ + expand: true, + cwd: '<%= config.app %>/images', + src: '{,*/}*.{gif,jpeg,jpg,png}', + dest: '<%= config.dist %>/images' + }] + } + }, + + svgmin: { + dist: { + files: [{ + expand: true, + cwd: '<%= config.app %>/images', + src: '{,*/}*.svg', + dest: '<%= config.dist %>/images' + }] + } + }, + + htmlmin: { + dist: { + options: { + collapseBooleanAttributes: true, + collapseWhitespace: true, + removeAttributeQuotes: true, + removeCommentsFromCDATA: true, + removeEmptyAttributes: true, + removeOptionalTags: true, + removeRedundantAttributes: true, + useShortDoctype: true + }, + files: [{ + expand: true, + cwd: '<%= config.dist %>', + src: '{,*/}*.html', + dest: '<%= config.dist %>' + }] + } + }, + + // By default, your `index.html`'s will take care of + // minification. These next options are pre-configured if you do not wish + // to use the Usemin blocks. + // cssmin: { + // dist: { + // files: { + // '<%= config.dist %>/styles/main.css': [ + // '.tmp/styles/{,*/}*.css', + // '<%= config.app %>/styles/{,*/}*.css' + // ] + // } + // } + // }, + // uglify: { + // dist: { + // files: { + // '<%= config.dist %>/scripts/scripts.js': [ + // '<%= config.dist %>/scripts/scripts.js' + // ] + // } + // } + // }, + // concat: { + // dist: {} + // }, + + // Copies remaining files to places other tasks can use + copy: { + dist: { + files: [{ + expand: true, + dot: true, + cwd: '<%= config.app %>', + dest: '<%= config.dist %>', + src: [ + '*.{ico,png,txt}', + '.htaccess', + 'images/{,*/}*.webp', + '{,*/}*.html', + 'styles/fonts/{,*/}*.*' + ] + }] + }, + styles: { + expand: true, + dot: true, + cwd: '<%= config.app %>/styles', + dest: '.tmp/styles/', + src: '{,*/}*.css' + } + }, + + // Run some tasks in parallel to speed up build process + concurrent: { + server: [ + 'sass:server', + 'copy:styles' + ], + test: [ + 'copy:styles' + ], + dist: [ + 'sass', + 'copy:styles', + 'imagemin', + 'svgmin' + ] + } + }); + + + grunt.registerTask('serve', function (target) { + if (target === 'dist') { + return grunt.task.run(['build', 'connect:dist:keepalive']); + } + + grunt.task.run([ + 'clean:server', + 'concurrent:server', + 'autoprefixer', + 'connect:livereload', + 'watch' + ]); + }); + + grunt.registerTask('server', function (target) { + grunt.log.warn('The `server` task has been deprecated. Use `grunt serve` to start a server.'); + grunt.task.run([target ? ('serve:' + target) : 'serve']); + }); + + grunt.registerTask('test', function (target) { + if (target !== 'watch') { + grunt.task.run([ + 'clean:server', + 'concurrent:test', + 'autoprefixer' + ]); + } + + grunt.task.run([ + 'connect:test', + 'mocha' + ]); + }); + + grunt.registerTask('build', [ + 'clean:dist', + 'useminPrepare', + 'concurrent:dist', + 'autoprefixer', + 'concat', + 'cssmin', + 'uglify', + 'copy:dist', + 'rev', + 'usemin', + 'htmlmin' + ]); + + grunt.registerTask('default', [ + 'newer:jshint', + 'test', + 'build' + ]); +}; diff --git a/html/app/favicon.ico b/html/app/favicon.ico new file mode 100644 index 0000000000000000000000000000000000000000..6527905307f19ba00762f9241f7eb535fa84a2f9 GIT binary patch literal 4286 zcmchaPe@cz6vpqQW1y54B@{_hhFD-kWPgyXjSGVaf);_51TESOlSPOdvy}@W5Q+** zs6~RrtlR}7(V|sCkP&1f7!5{Hixw@4+x@+HXSm*Z^WGalm2d8S=brO@=iGm9MyZ7P zPo)%}YN|=8W~EfSfibDm2H3qnGq$y%h@zqVv#zn@@WvhIGJ8*ECePe@roq(*vwGys z4?Q;bI~MRIM&jXu6Yg@wqQ#8&8x#z55E}ONd3<&rw_h!5AbBx{CcZ%&z736jHxFa0 zsBLqly3+dQ%MZGH{QU}GW6bsq=@$a@sXtac^<8>8uP>*+d!Qdtv&&mnKlvE_T-+SC z*QNCVwcvq%+&DDc+T}Uf(2_FavDN{-&hCpIs?aW=A$mcrzyD+9(025i1~K&uVf&w4 zItQLK9T{7k?s@bnU*&p+<^UI*aHA1aH+Fo^PAzM|xjNK09?2V(Cme7IFB(BP?7#at z(>DB3w`AUFS~=(LUBdZ>v-SG4J~%Mrfj&05Z)oj13l5tbEq4x>8+;FC0Dvr zbJY#7PS$+yE_Cf7gxqQEC@RoZX5J^}71l+`Q~qnOF4D za`lhjUuqZa-sj)EHDleV2i|mc!Ly-@7IwzPM{?pBUt(+@IHi8HTz#Iq9)9h|hrL3) zfOT#@|5$JCxmRjsOj>&kUt(m8*57|W(FoE`CX*8edYv%j=3sR5>!hvglJ#@8K6j$g z&IuUbRC_{)p}sbyx%UD6Fki;t6nDk0gT5&6Q_at7FbVVOu?4VK{oR#!kyYbCc;<4+LITzoZ8-~O5L+9MiLHL4NyME>! z;Ky7<)UR!gN_~GXhMvPMHNB;EmmIK}eHD&~cRx89jth}IM#tU%ablw0|GxfE9IjRR zl-)b-IvC#UD!IewzPL77SI>R+?}<2ERr|R2o~zCC8rJUR8>DI5*0O$6+k~wZ)Mt;b z(Hul-OFl+F))}lK&&Yi*+S2kJmHDbdBWOQnaSA6S|#* + + + + Garry's Mod MediaPlayer + + + + + + + + + + + + + + + + + + + diff --git a/html/app/scripts/main.js b/html/app/scripts/main.js new file mode 100644 index 0000000..670c505 --- /dev/null +++ b/html/app/scripts/main.js @@ -0,0 +1,16 @@ +'use strict'; + +window.MP = (function () { + + var elem = document.body; + + return { + + setHtml: function (html) { + elem.innerHTML = html; + console.log(elem.innerHTML); + } + + }; + +}()); diff --git a/html/app/styles/main.scss b/html/app/styles/main.scss new file mode 100644 index 0000000..a700b81 --- /dev/null +++ b/html/app/styles/main.scss @@ -0,0 +1,17 @@ +// bower:scss +// endbower + +html, body { + margin: 0; + padding: 0; + width: 100%; + height: 100%; +} + +* { box-sizing: border-box } + +body { + background-color: #ececec; + color: #313131; + overflow: hidden; +} diff --git a/html/app/vimeo.html b/html/app/vimeo.html new file mode 100644 index 0000000..391c15c --- /dev/null +++ b/html/app/vimeo.html @@ -0,0 +1,95 @@ + + + + + Garry's Mod MediaPlayer + + + + + + + + + + + + + + + + + + + + diff --git a/html/app/vimeo_playground.html b/html/app/vimeo_playground.html new file mode 100644 index 0000000..f3a28fd --- /dev/null +++ b/html/app/vimeo_playground.html @@ -0,0 +1,642 @@ + + + + + Vimeo Player API Playground + + + + +

+
+

Player API Playground

+
+
+
+

Vimeo Player 1

+ +
+
Simple Buttons (buttons with simple API calls)
+
+
+
+
+
+
Modifier Buttons (buttons that modify the player)
+
+
+
+
+
+
+
+
+
Getters (buttons that get back a value; logged out in the console)
+
+
+
+
+
+
+
+
+
+
+
+
Event Listeners
+
+
+
+
+
+
+
+
+
+
Console
+
+
+
+ +
+ + + + + diff --git a/html/bower.json b/html/bower.json new file mode 100644 index 0000000..a92a0c5 --- /dev/null +++ b/html/bower.json @@ -0,0 +1,8 @@ +{ + "name": "html", + "private": true, + "dependencies": { + "jquery": "~1.11.0" + }, + "devDependencies": {} +} diff --git a/html/package.json b/html/package.json new file mode 100644 index 0000000..5ad25c9 --- /dev/null +++ b/html/package.json @@ -0,0 +1,33 @@ +{ + "name": "GMod MediaPlayer", + "version": "0.0.0", + "dependencies": {}, + "devDependencies": { + "grunt": "~0.4.1", + "grunt-contrib-copy": "~0.5.0", + "grunt-contrib-concat": "~0.3.0", + "grunt-contrib-uglify": "~0.4.0", + "grunt-contrib-sass": "~0.7.3", + "grunt-contrib-jshint": "~0.9.2", + "grunt-contrib-cssmin": "~0.9.0", + "grunt-contrib-connect": "~0.7.1", + "grunt-contrib-clean": "~0.5.0", + "grunt-contrib-htmlmin": "~0.2.0", + "grunt-bower-install": "~1.4.0", + "grunt-contrib-imagemin": "~0.6.0", + "grunt-contrib-watch": "~0.6.1", + "grunt-rev": "~0.1.0", + "grunt-autoprefixer": "~0.7.2", + "grunt-usemin": "~2.1.0", + "grunt-mocha": "~0.4.10", + "grunt-newer": "~0.7.0", + "grunt-svgmin": "~0.4.0", + "grunt-concurrent": "~0.5.0", + "load-grunt-tasks": "~0.4.0", + "time-grunt": "~0.3.1", + "jshint-stylish": "~0.1.5" + }, + "engines": { + "node": ">=0.10.0" + } +} diff --git a/html/test/.bowerrc b/html/test/.bowerrc new file mode 100644 index 0000000..44491d3 --- /dev/null +++ b/html/test/.bowerrc @@ -0,0 +1,3 @@ +{ + "directory": "bower_components" +} diff --git a/html/test/bower.json b/html/test/bower.json new file mode 100644 index 0000000..dad9a4a --- /dev/null +++ b/html/test/bower.json @@ -0,0 +1,9 @@ +{ + "name": "html", + "private": true, + "dependencies": { + "chai": "~1.8.0", + "mocha": "~1.14.0" + }, + "devDependencies": {} +} diff --git a/html/test/index.html b/html/test/index.html new file mode 100644 index 0000000..d18b7d3 --- /dev/null +++ b/html/test/index.html @@ -0,0 +1,27 @@ + + + + + + Mocha Spec Runner + + + +
+ + + + + + + + + + + + + diff --git a/html/test/spec/test.js b/html/test/spec/test.js new file mode 100644 index 0000000..adfd614 --- /dev/null +++ b/html/test/spec/test.js @@ -0,0 +1,13 @@ +/* global describe, it */ + +(function () { + 'use strict'; + + describe('Give it some context', function () { + describe('maybe a bit more context here', function () { + it('should run here few assertions', function () { + + }); + }); + }); +})(); diff --git a/lua/autorun/includes/extensions/cl_draw.lua b/lua/autorun/includes/extensions/cl_draw.lua new file mode 100644 index 0000000..63bbe93 --- /dev/null +++ b/lua/autorun/includes/extensions/cl_draw.lua @@ -0,0 +1,76 @@ +local CurTime = CurTime +local RealTime = RealTime +local FrameTime = FrameTime +local Material = Material +local pairs = pairs +local tonumber = tonumber +local table = table +local string = string +local type = type +local surface = surface +local HSVToColor = HSVToColor +local Lerp = Lerp +local Msg = Msg +local math = math +local draw = draw +local cam = cam +local Matrix = Matrix +local Angle = Angle +local Vector = Vector +local setmetatable = setmetatable +local ScrW = ScrW +local ScrH = ScrH +local ValidPanel = ValidPanel +local Color = Color +local color_white = color_white +local color_black = color_black +local TEXT_ALIGN_LEFT = TEXT_ALIGN_LEFT +local TEXT_ALIGN_RIGHT = TEXT_ALIGN_RIGHT +local TEXT_ALIGN_TOP = TEXT_ALIGN_TOP +local TEXT_ALIGN_BOTTOM = TEXT_ALIGN_BOTTOM +local TEXT_ALIGN_CENTER = TEXT_ALIGN_CENTER + +function draw.HTMLPanel( panel, w, h ) + + if not ValidPanel( panel ) then return end + if not (w and h) then return end + + panel:UpdateHTMLTexture() + + local pw, ph = panel:GetSize() + + -- Convert to scalar + w = w / pw + h = h / ph + + -- Fix for non-power-of-two html panel size + pw = math.CeilPower2(pw) + ph = math.CeilPower2(ph) + + surface.SetDrawColor( 255, 255, 255, 255 ) + surface.SetMaterial( panel:GetHTMLMaterial() ) + surface.DrawTexturedRect( 0, 0, w * pw, h * ph ) + +end + +draw.HTMLTexture = draw.HTMLPanel + +function draw.HTMLMaterial( mat, w, h ) + + if not (w and h) then return end + + local pw, ph = w, h + + -- Convert to scalar + w = w / pw + h = h / ph + + -- Fix for non-power-of-two html panel size + pw = math.CeilPower2(pw) + ph = math.CeilPower2(ph) + + surface.SetDrawColor( 255, 255, 255, 255 ) + surface.SetMaterial( mat ) + surface.DrawTexturedRect( 0, 0, w * pw, h * ph ) + +end diff --git a/lua/autorun/includes/extensions/sh_file.lua b/lua/autorun/includes/extensions/sh_file.lua new file mode 100644 index 0000000..78e1291 --- /dev/null +++ b/lua/autorun/includes/extensions/sh_file.lua @@ -0,0 +1,10 @@ +local file = file + +function file.ReadJSON( name, path ) + path = path or "DATA" + + local json = file.Read( name, path ) + if not json then return end + + return util.JSONToTable(json) +end diff --git a/lua/autorun/includes/extensions/sh_math.lua b/lua/autorun/includes/extensions/sh_math.lua new file mode 100644 index 0000000..2d2d6a6 --- /dev/null +++ b/lua/autorun/includes/extensions/sh_math.lua @@ -0,0 +1,9 @@ +local math = math +local ceil = math.ceil +local log = math.log +local pow = math.pow + +-- Ceil the given number to the largest power of two +function math.CeilPower2(n) + return pow(2, ceil(log(n) / log(2))) +end diff --git a/lua/autorun/includes/extensions/sh_table.lua b/lua/autorun/includes/extensions/sh_table.lua new file mode 100644 index 0000000..4484dcd --- /dev/null +++ b/lua/autorun/includes/extensions/sh_table.lua @@ -0,0 +1,21 @@ +--- +-- Method for easily grabbing a value from a table without checking that each +-- fragment exists. +-- +-- @param tbl Table +-- @param key e.g. "json.key.fragments" +-- +function table.Lookup( tbl, key ) + local fragments = string.Split(key, '.') + local value = tbl + + for _, fragment in ipairs(fragments) do + value = value[fragment] + + if not value then + return nil + end + end + + return value +end diff --git a/lua/autorun/includes/extensions/sh_url.lua b/lua/autorun/includes/extensions/sh_url.lua new file mode 100644 index 0000000..736ff59 --- /dev/null +++ b/lua/autorun/includes/extensions/sh_url.lua @@ -0,0 +1,452 @@ +----------------------------------------------------------------------------- +-- URI parsing, composition and relative URL resolution +-- LuaSocket toolkit. +-- Author: Diego Nehab +-- RCS ID: $Id: url.lua,v 1.38 2006/04/03 04:45:42 diego Exp $ +----------------------------------------------------------------------------- + +----------------------------------------------------------------------------- +-- Declare module +----------------------------------------------------------------------------- +local string = string +local base = _G +local table = table +local pairs = pairs +local ipairs = ipairs +local tonumber = tonumber +local type = type +module("url") + +----------------------------------------------------------------------------- +-- Module version +----------------------------------------------------------------------------- +_VERSION = "URL 1.0.1" + +----------------------------------------------------------------------------- +-- HTML Entity Translation Table +-- http://lua-users.org/lists/lua-l/2005-10/msg00328.html +----------------------------------------------------------------------------- +local entities = { + [' '] = ' ', + ['¡'] = '¡', + ['¢'] = '¢', + ['£'] = '£', + ['¤'] = '¤', + ['¥'] = '¥', + ['¦'] = '¦', + ['§'] = '§', + ['¨'] = '¨', + ['©'] = '©', + ['ª'] = 'ª', + ['«'] = '«', + ['¬'] = '¬', + ['­'] = '­', + ['®'] = '®', + ['¯'] = '¯', + ['°'] = '°', + ['±'] = '±', + ['²'] = '²', + ['³'] = '³', + ['´'] = '´', + ['µ'] = 'µ', + ['¶'] = '¶', + ['·'] = '·', + ['¸'] = '¸', + ['¹'] = '¹', + ['º'] = 'º', + ['»'] = '»', + ['¼'] = '¼', + ['½'] = '½', + ['¾'] = '¾', + ['¿'] = '¿', + ['À'] = 'À', + ['Á'] = 'Á', + ['Â'] = 'Â', + ['Ã'] = 'Ã', + ['Ä'] = 'Ä', + ['Å'] = 'Å', + ['Æ'] = 'Æ', + ['Ç'] = 'Ç', + ['È'] = 'È', + ['É'] = 'É', + ['Ê'] = 'Ê', + ['Ë'] = 'Ë', + ['Ì'] = 'Ì', + ['Í'] = 'Í', + ['Î'] = 'Î', + ['Ï'] = 'Ï', + ['Ð'] = 'Ð', + ['Ñ'] = 'Ñ', + ['Ò'] = 'Ò', + ['Ó'] = 'Ó', + ['Ô'] = 'Ô', + ['Õ'] = 'Õ', + ['Ö'] = 'Ö', + ['×'] = '×', + ['Ø'] = 'Ø', + ['Ù'] = 'Ù', + ['Ú'] = 'Ú', + ['Û'] = 'Û', + ['Ü'] = 'Ü', + ['Ý'] = 'Ý', + ['Þ'] = 'Þ', + ['ß'] = 'ß', + ['à'] = 'à', + ['á'] = 'á', + ['â'] = 'â', + ['ã'] = 'ã', + ['ä'] = 'ä', + ['å'] = 'å', + ['æ'] = 'æ', + ['ç'] = 'ç', + ['è'] = 'è', + ['é'] = 'é', + ['ê'] = 'ê', + ['ë'] = 'ë', + ['ì'] = 'ì', + ['í'] = 'í', + ['î'] = 'î', + ['ï'] = 'ï', + ['ð'] = 'ð', + ['ñ'] = 'ñ', + ['ò'] = 'ò', + ['ó'] = 'ó', + ['ô'] = 'ô', + ['õ'] = 'õ', + ['ö'] = 'ö', + ['÷'] = '÷', + ['ø'] = 'ø', + ['ù'] = 'ù', + ['ú'] = 'ú', + ['û'] = 'û', + ['ü'] = 'ü', + ['ý'] = 'ý', + ['þ'] = 'þ', + ['ÿ'] = 'ÿ', + ['"'] = '"', + ["'"] = ''', + ['<'] = '<', + ['>'] = '>', + ['&'] = '&' +} + +function htmlentities(s) + for k, v in pairs(entities) do + s = string.gsub(s, k, v) + end + return s +end + +function htmlentities_decode(s) + for k, v in pairs(entities) do + s = string.gsub(s, v, k) + end + return s +end + +----------------------------------------------------------------------------- +-- Encodes a string into its escaped hexadecimal representation +-- Input +-- s: binary string to be encoded +-- Returns +-- escaped representation of string binary +----------------------------------------------------------------------------- +function escape(s) + return string.gsub(s, "([^A-Za-z0-9_])", function(c) + return string.format("%%%02x", string.byte(c)) + end) +end + +----------------------------------------------------------------------------- +-- Protects a path segment, to prevent it from interfering with the +-- url parsing. +-- Input +-- s: binary string to be encoded +-- Returns +-- escaped representation of string binary +----------------------------------------------------------------------------- +local function make_set(t) + local s = {} + for i,v in ipairs(t) do + s[t[i]] = 1 + end + return s +end + +-- these are allowed withing a path segment, along with alphanum +-- other characters must be escaped +local segment_set = make_set { + "-", "_", ".", "!", "~", "*", "'", "(", + ")", ":", "@", "&", "=", "+", "$", ",", +} + +local function protect_segment(s) + return string.gsub(s, "([^A-Za-z0-9_])", function (c) + if segment_set[c] then return c + else return string.format("%%%02x", string.byte(c)) end + end) +end + +----------------------------------------------------------------------------- +-- Encodes a string into its escaped hexadecimal representation +-- Input +-- s: binary string to be encoded +-- Returns +-- escaped representation of string binary +----------------------------------------------------------------------------- +function unescape(s) + return string.gsub(s, "%%(%x%x)", function(hex) + return string.char(tonumber(hex, 16)) + end) +end + +----------------------------------------------------------------------------- +-- Builds a path from a base path and a relative path +-- Input +-- base_path +-- relative_path +-- Returns +-- corresponding absolute path +----------------------------------------------------------------------------- +local function absolute_path(base_path, relative_path) + if string.sub(relative_path, 1, 1) == "/" then return relative_path end + local path = string.gsub(base_path, "[^/]*$", "") + path = path .. relative_path + path = string.gsub(path, "([^/]*%./)", function (s) + if s ~= "./" then return s else return "" end + end) + path = string.gsub(path, "/%.$", "/") + local reduced + while reduced ~= path do + reduced = path + path = string.gsub(reduced, "([^/]*/%.%./)", function (s) + if s ~= "../../" then return "" else return s end + end) + end + path = string.gsub(reduced, "([^/]*/%.%.)$", function (s) + if s ~= "../.." then return "" else return s end + end) + return path +end + +----------------------------------------------------------------------------- +-- Parses a url and returns a table with all its parts according to RFC 2396 +-- The following grammar describes the names given to the URL parts +-- ::= :///;?# +-- ::= @: +-- ::= [:] +-- :: = {/} +-- Input +-- url: uniform resource locator of request +-- default: table with default values for each field +-- Returns +-- table with the following fields, where RFC naming conventions have +-- been preserved: +-- scheme, authority, userinfo, user, password, host, port, +-- path, params, query, fragment +-- Obs: +-- the leading '/' in {/} is considered part of +----------------------------------------------------------------------------- +function parse(url, default) + -- initialize default parameters + local parsed = {} + for i,v in pairs(default or parsed) do parsed[i] = v end + -- empty url is parsed to nil + if not url or url == "" then return nil, "invalid url" end + -- remove whitespace + -- url = string.gsub(url, "%s", "") + -- get fragment + url = string.gsub(url, "#(.*)$", function(f) + parsed.fragment = f + return "" + end) + -- get scheme + url = string.gsub(url, "^([%w][%w%+%-%.]*)%://", + function(s) parsed.scheme = s; return "" end) + -- get authority + url = string.gsub(url, "^([^/%?]*)", function(n) + parsed.authority = n + return "" + end) + -- get query stringing + url = string.gsub(url, "%?(.*)", function(q) + parsed.query = q + return "" + end) + -- get params + url = string.gsub(url, "%;(.*)", function(p) + parsed.params = p + return "" + end) + -- path is whatever was left + if url ~= "" then parsed.path = url else parsed.path = "/" end + local authority = parsed.authority + if not authority then return parsed end + authority = string.gsub(authority,"^([^@]*)@", + function(u) parsed.userinfo = u; return "" end) + authority = string.gsub(authority, ":([^:]*)$", + function(p) parsed.port = p; return "" end) + if authority ~= "" then parsed.host = authority end + local userinfo = parsed.userinfo + if not userinfo then return parsed end + userinfo = string.gsub(userinfo, ":([^:]*)$", + function(p) parsed.password = p; return "" end) + parsed.user = userinfo + return parsed +end + +----------------------------------------------------------------------------- +-- Parses the url and also seperates the query terms into a table +----------------------------------------------------------------------------- +function parse2(url, default) + local parsed = parse(url, default) + + if parsed.query then + local prmstr = parsed.query + local prmarr = string.Explode("&", prmstr) + local params = {} + + for i = 1, #prmarr do + local tmparr = string.Explode("=", prmarr[i]) + params[tmparr[1]] = tmparr[2] + end + + parsed.query = params + end + + if parsed.fragment then + local prmstr = parsed.fragment + local prmarr = string.Explode("&", prmstr) + local params = {} + + for i = 1, #prmarr do + local tmparr = string.Explode("=", prmarr[i]) + params[tmparr[1]] = tmparr[2] + end + + parsed.fragment = params + end + + return parsed +end + +----------------------------------------------------------------------------- +-- Rebuilds a parsed URL from its components. +-- Components are protected if any reserved or unallowed characters are found +-- Input +-- parsed: parsed URL, as returned by parse +-- Returns +-- a stringing with the corresponding URL +----------------------------------------------------------------------------- +function build(parsed) + local ppath = parse_path(parsed.path or "") + local url = build_path(ppath) + local url = (parsed.path or ""):gsub("[^/]+", unescape) + local url = url:gsub("[^/]*", protect_segment) + if parsed.params then url = url .. ";" .. parsed.params end + if parsed.query then url = url .. "?" .. parsed.query end + local authority = parsed.authority + if parsed.host then + authority = parsed.host + if parsed.port then authority = authority .. ":" .. parsed.port end + local userinfo = parsed.userinfo + if parsed.user then + userinfo = parsed.user + if parsed.password then + userinfo = userinfo .. ":" .. parsed.password + end + end + if userinfo then authority = userinfo .. "@" .. authority end + end + if authority then url = "//" .. authority .. url end + if parsed.scheme then url = parsed.scheme .. ":" .. url end + if parsed.fragment then url = url .. "#" .. parsed.fragment end + -- url = string.gsub(url, "%s", "") + return url +end + +----------------------------------------------------------------------------- +-- Builds a absolute URL from a base and a relative URL according to RFC 2396 +-- Input +-- base_url +-- relative_url +-- Returns +-- corresponding absolute url +----------------------------------------------------------------------------- +function absolute(base_url, relative_url) + if type(base_url) == "table" then + base_parsed = base_url + base_url = build(base_parsed) + else + base_parsed = parse(base_url) + end + local relative_parsed = parse(relative_url) + if not base_parsed then return relative_url + elseif not relative_parsed then return base_url + elseif relative_parsed.scheme then return relative_url + else + relative_parsed.scheme = base_parsed.scheme + if not relative_parsed.authority or relative_parsed.authority == "" then + relative_parsed.authority = base_parsed.authority + if not relative_parsed.path then + relative_parsed.path = base_parsed.path + if not relative_parsed.params then + relative_parsed.params = base_parsed.params + if not relative_parsed.query then + relative_parsed.query = base_parsed.query + end + end + else + relative_parsed.path = absolute_path(base_parsed.path or "", + relative_parsed.path) + end + end + return build(relative_parsed) + end +end + +----------------------------------------------------------------------------- +-- Breaks a path into its segments, unescaping the segments +-- Input +-- path +-- Returns +-- segment: a table with one entry per segment +----------------------------------------------------------------------------- +function parse_path(path) + local parsed = {} + path = path or "" + --path = string.gsub(path, "%s", "") + string.gsub(path, "([^/]*)", function (s) table.insert(parsed, s) end) + for i = 1, table.getn(parsed) do + parsed[i] = unescape(parsed[i]) + end + if string.sub(path, 1, 1) == "/" then parsed.is_absolute = 1 end + if string.sub(path, -1, -1) == "/" then parsed.is_directory = 1 end + return parsed +end + +----------------------------------------------------------------------------- +-- Builds a path component from its segments, escaping protected characters. +-- Input +-- parsed: path segments +-- unsafe: if true, segments are not protected before path is built +-- Returns +-- path: corresponding path stringing +----------------------------------------------------------------------------- +function build_path(parsed, unsafe) + local path = "" + local escape = unsafe and function(x) return x end or protect_segment + local n = table.getn(parsed) + for i = 1, n-1 do + if parsed[i]~= "" or parsed[i+1] == "" then + path = path .. escape(parsed[i]) + if i < n - 1 or parsed[i+1] ~= "" then path = path .. "/" end + end + end + if n > 0 then + path = path .. escape(parsed[n]) + if parsed.is_directory then path = path .. "/" end + end + if parsed.is_absolute then path = "/" .. path end + return path +end \ No newline at end of file diff --git a/lua/autorun/includes/modules/EventEmitter.lua b/lua/autorun/includes/modules/EventEmitter.lua new file mode 100644 index 0000000..b752509 --- /dev/null +++ b/lua/autorun/includes/modules/EventEmitter.lua @@ -0,0 +1,179 @@ +--- +-- EventEmitter +-- +-- Based off of Wolfy87's JavaScript EventEmitter +-- +local EventEmitter = {} + +local function indexOfListener(listeners, listener) + local i = #listeners + while i > 0 do + if listeners[i].listener == listener then + return i + end + i = i - 1 + end + + return -1 +end + +function EventEmitter:new(obj) + if obj then + table.Inherit(obj, self) + else + return setmetatable({}, self) + end +end + +function EventEmitter:getListeners(evt) + local events = self:_getEvents() + local response, key + + -- TODO: accept pattern matching + + if not events[evt] then + events[evt] = {} + end + + response = events[evt] + + return response +end + +--[[function EventEmitter:flattenListeners(listeners) + +end]] + +function EventEmitter:getListenersAsObject(evt) + local listeners = self:getListeners(evt) + local response + + if type(listeners) == 'table' then + response = {} + response[evt] = listeners + end + + return response or listeners +end + +function EventEmitter:addListener(evt, listener) + local listeners = self:getListenersAsObject(evt) + local listenerIsWrapped = type(listener) == 'table' + local key + + for key, _ in pairs(listeners) do + if rawget(listeners, key) and indexOfListener(listeners[key], listener) == -1 then + table.insert(listeners[key], listenerIsWrapped and listener or { + listener = listener, + once = false + }) + end + end + + return self +end + +EventEmitter.on = EventEmitter.addListener + +function EventEmitter:addOnceListener(evt, listener) + return self:addListener(evt, { + listener = listener, + once = true + }) +end + +EventEmitter.once = EventEmitter.addOnceListener + +function EventEmitter:removeListener(evt, listener) + local listeners = self:getListenersAsObject(evt) + local index, key + + for key, _ in pairs(listeners) do + if rawget(listeners, key) then + index = indexOfListener(listeners[key], listener) + + if index ~= -1 then + table.remove(listeners[key], index) + end + end + end + + return self +end + +EventEmitter.off = EventEmitter.removeListener + +--[[function EventEmitter:addListeners(evt, listeners) + +end]] + +function EventEmitter:removeEvent(evt) + local typeStr = type(evt) + local events = self:_getEvents() + local key + + if typeStr == 'string' then + events[evt] = nil + else + self._events = nil + end + + return self +end + +EventEmitter.removeAllListeners = EventEmitter.removeEvent + +function EventEmitter:emitEvent(evt, ...) + local listeners = self:getListenersAsObject(evt) + local listener, i, key, response + + for key, _ in pairs(listeners) do + if rawget(listeners, key) then + i = #listeners[key] + + while i > 0 do + listener = listeners[key][i] + + if listener.once == true then + self:removeListener(evt, listener.listener) + end + + response = listener.listener(...) + + if response == self:_getOnceReturnValue() then + self:removeListener(evt, listener.listener) + end + + i = i - 1 + end + end + end + + return self +end + +EventEmitter.trigger = EventEmitter.emitEvent +EventEmitter.emit = EventEmitter.emitEvent + +function EventEmitter:setOnceReturnValue(value) + self._onceReturnValue = value + return self +end + +function EventEmitter:_getOnceReturnValue() + if rawget(self, '_onceReturnValue') then + return self._onceReturnValue + else + return true + end +end + +function EventEmitter:_getEvents() + if not rawget(self, '_events') then + self._events = {} + end + + return self._events +end + +_G.EventEmitter = EventEmitter diff --git a/lua/autorun/includes/modules/browserpool.lua b/lua/autorun/includes/modules/browserpool.lua new file mode 100644 index 0000000..5c7b82c --- /dev/null +++ b/lua/autorun/includes/modules/browserpool.lua @@ -0,0 +1,299 @@ +if browserpool then return end -- ignore Lua refresh + +local table = table +local vgui = vgui + +_G.browserpool = {} + +--- +-- Debug variable which will allow outputting messages if enabled. +-- @type boolean +-- +local DEBUG = true + +--- +-- Array of available, pooled browsers +-- @type table +-- +local available = {} + +--- +-- Array of active, pooled browsers. +-- @type table +-- +local active = {} + +--- +-- Array of pending requests for a browser. +-- @type table +-- +local pending = {} + +--- +-- Minimum number of active browsers to be pooled. +-- @type Number +-- +local numMin = 2 + +--- +-- Maximum number of active browsers to be pooled. +-- @type Number +-- +local numMax = 5 + +--- +-- Number of currently active browsers. +-- @type Number +-- +local numActive = 0 + +--- +-- Number of currently pending browser requests. +-- @type Number +-- +local numPending = 0 + +--- +-- Number of total browser requests. +-- @type Number +-- +local numRequests = 0 + +--- +-- Default URL to set browsers on setup/teardown. +-- @type String +-- +local defaultUrl = "about:blank" + +--- +-- JavaScript code to remove an object's property. +-- @type String +-- +local JS_RemoveProp = "delete %s.%s;" + +--- +-- Helper function to setup/teardown a browser panel. +-- +-- @param panel? Browser panel to be cleaned up. +-- @return Panel DMediaPlayerHTML panel instance. +-- +local function setupPanel( panel ) + + -- Create a new panel if it wasn't passed in + if panel then + panel:Stop() + else + panel = vgui.Create("DMediaPlayerHTML") + end + + -- Hide panel + -- panel:SetSize(0, 0) + panel:SetPos(0, 0) + + -- Disable input + panel:SetKeyBoardInputEnabled(false) + panel:SetMouseInputEnabled(false) + + -- Browser panels are usually manually drawn, use a regular panel if not + panel:SetPaintedManually(true) + + -- Set default URL + panel:OpenURL( defaultUrl ) + + -- Remove any added function callbacks + for obj, tbl in pairs(panel.Callbacks) do + if obj ~= "console" then + for funcname, _ in pairs(tbl) do + panel:QueueJavascript(JS_RemoveProp:format(obj, funcname)) + end + end + end + + return panel + +end + +--- +-- Local function for removing cancelled browser promises via closures. +-- +-- @param Promise Browser bromise. +-- @return Boolean Success status. +-- +local function removePromise( promise ) + local id = promise:GetId() + + if not table.remove( pending, id ) then + ErrorNoHalt( "browserpool: Failed to remove promise.\n" ) + print( promise, id ) + debug.Trace() + return false + end + + return true +end + +--- +-- Browser promise for resolving browser requests when there isn't an available +-- browser at the time of request. +-- +local BrowserPromise = {} +local BrowserPromiseMeta = { __index = BrowserPromise } + +function BrowserPromise:New( callback, id ) + return setmetatable( + { __cb = callback, __id = id or -1 }, + BrowserPromiseMeta + ) +end + +function BrowserPromise:GetId() + return self.__id +end + +function BrowserPromise:Resolve( value ) + self.__cb(value) +end + +function BrowserPromise:Cancel( reason ) + self.__cb(false, reason) + removePromise(self) +end + +--- +-- Retrieves an available browser panel from the pool. Otherwise, a new panel +-- will be created. +-- +-- @return Panel DMediaPlayerHTML panel instance. +-- +function browserpool.get( callback ) + + numRequests = numRequests + 1 + + if DEBUG then + print( string.format("browserpool: get [Active: %s][Available: %s][Pending: %s]", + numActive, #available, numPending ) ) + end + + local panel + + -- Check if there's an available panel + if #available > 0 then + + panel = table.remove( available ) + table.insert( active, panel ) + + callback( panel ) + + elseif numActive < numMax then -- create a new panel + + panel = setupPanel() + numActive = numActive + 1 + + if DEBUG then + print( "browserpool: Spawned new browser [Active: "..numActive.."]" ) + end + + table.insert( active, panel ) + callback( panel ) + + else -- wait for an available browser + + local promise = BrowserPromise:New( callback, numRequests ) + + pending[numRequests] = promise + numPending = numPending + 1 + + return promise + + end + +end + +--- +-- Releases the given browser panel from the active pool. +-- +-- Remember to unset references to the browser instance after releasing: +-- browserpool.release( self.Browser ) +-- self.Browser = nil +-- +-- @param panel Browser panel to be released. +-- @return boolean Whether the panel was successfully removed. +-- +function browserpool.release( panel ) + + if not panel then return end + + local key = table.KeyFromValue( active, panel ) + + -- Unable to find active browser panel + if not key then + ErrorNoHalt( "browserpool: Attempted to release unactive browser.\n" ) + debug.Trace() + + -- Remove browser even if the request was invalid + if ValidPanel(panel) then + panel:Remove() + end + + return false + end + + -- Resolve an open promise if one exists + if numPending > 0 then + + -- Get the earliest request first + -- TODO: this seems to grab nil valued keys? + local id = table.GetFirstKey( pending ) + + local promise = pending[id] + pending[id] = nil + + -- Cleanup panel + setupPanel( panel ) + + promise:Resolve( panel ) + numPending = numPending - 1 + + else + + if not table.remove( active, key ) then + ErrorNoHalt( "browserpool: Failed to remove panel from active browsers.\n" ) + debug.Trace() + + -- Remove browser even if the request was invalid + if ValidPanel(panel) then + panel:Remove() + end + + return false + end + + -- Remove panel if there are more active than the minimum pool size + if numActive > numMin then + + panel:Remove() + numActive = numActive - 1 + + if DEBUG then + print( "browserpool: Destroyed browser [Active: "..numActive.."]" ) + end + + else + + -- Cleanup panel + setupPanel( panel ) + + -- Add to the pool + table.insert( available, panel ) + + if DEBUG then + print( "browserpool: Pooled browser [Active: "..numActive.."]" ) + end + + end + + end + + return true + +end diff --git a/lua/autorun/includes/modules/control.lua b/lua/autorun/includes/modules/control.lua new file mode 100644 index 0000000..b614b30 --- /dev/null +++ b/lua/autorun/includes/modules/control.lua @@ -0,0 +1,96 @@ +local IsValid = IsValid +local pairs = pairs +local RealTime = RealTime +local type = type +local IsKeyDown = input.IsKeyDown +local IsGameUIVisible = gui.IsGameUIVisible +local IsConsoleVisible = gui.IsConsoleVisible + +_G.control = {} + +local HoldTime = 0.3 + +local LastPress = nil +local LastKey = nil +local KeyControls = {} + +local function dispatch(name, func, down, held) + -- Use same behavior as the hook system + if type(name) == 'table' then + if IsValid(name) then + func( name, down, held ) + else + handles[ name ] = nil + end + else + func( down, held ) + end +end + +local function InputThink() + + if IsGameUIVisible() or IsConsoleVisible() then return end + + for key, handles in pairs( KeyControls ) do + for name, tbl in pairs( handles ) do + + if tbl.Enabled then + + -- Key hold (repeat press) + if tbl.LastPress and tbl.LastPress + HoldTime < RealTime() then + dispatch(name, tbl.Toggle, true, true) + tbl.LastPress = RealTime() + end + + -- Key release + if not IsKeyDown( key ) then + dispatch(name, tbl.Toggle, false) + tbl.Enabled = false + end + + else + + -- Key press + if IsKeyDown( key ) then + dispatch(name, tbl.Toggle, true) + tbl.Enabled = true + tbl.LastPress = RealTime() + end + + end + + end + end + +end +hook.Add( "Think", "InputManagerThink", InputThink ) + +function control.Add( key, name, onToggle ) + + if not (key and onToggle) then return end + + if not KeyControls[ key ] then + KeyControls[ key ] = {} + end + + KeyControls[ key ][ name ] = { + Enabled = false, + LastPress = 0, + Toggle = onToggle + --[[Toggle = function(...) + local msg, err = pcall( onToggle, ... ) + if err then + print( "ERROR: " .. msg ) + end + end]] + } + +end + +function control.Remove( key, name ) + + if not KeyControls[ key ] then return end + + KeyControls[ key ][ name ] = nil + +end diff --git a/lua/autorun/includes/modules/htmlmaterial.lua b/lua/autorun/includes/modules/htmlmaterial.lua new file mode 100644 index 0000000..226d821 --- /dev/null +++ b/lua/autorun/includes/modules/htmlmaterial.lua @@ -0,0 +1,140 @@ +local cache = {} +local downloads = {} +local styles = {} + +local embedHtml = [[ + + + + + + + + + + + + +]] + +local UpdateTimerName = "HtmlMatUpdate" +local TimerRunning = false + +local function updateCache(download) + download.browser:UpdateHTMLTexture() + cache[download.key] = download.browser:GetHTMLMaterial() +end + +local function updateMaterials() + for _, download in ipairs(downloads) do + updateCache(download) + end +end + +local function onImageLoaded(key, browser) + local idx + + for k, v in pairs(downloads) do + if v.key == key then + idx = k + break + end + end + + if idx > 0 then + local download = downloads[idx] + browserpool.release(browser) + table.remove(downloads, idx) + end + + if #downloads == 0 and TimerRunning then + timer.Destroy(UpdateTimerName) + TimerRunning = false + end +end + +local DefaultMat = Material("vgui/white") +local DefaultWidth = 128 + +local function enqueueUrl( url, styleName, key ) + cache[key] = DefaultMat + + browserpool.get(function(browser) + local style = styles[styleName] or {} + local w = style.width or DefaultWidth + local h = style.height or w + + browser:SetSize( w, h ) + + local download = { + url = url, + key = key, + browser = browser + } + + table.insert(downloads, download) + + browser:AddFunction("gmod", "imageLoaded", function() + updateCache(download) + onImageLoaded(key, browser) + end) + + if not TimerRunning then + timer.Create(UpdateTimerName, 0.05, 0, updateMaterials) + timer.Start(UpdateTimerName) + TimerRunning = true + end + + local html = embedHtml:format(url, style.css or '') + browser:SetHTML( html ) + end) +end + +--- +-- Renders a URL as a material. +-- +-- @param url URL. +-- @param style HTMLMaterial style. +-- +function HTMLMaterial( url, style ) + local key + + -- Build unique key for material + if style then + key = table.concat({url, '@', style}) + else + key = url + end + + -- Enqueue the URL to be downloaded if it hasn't been loaded yet. + if cache[key] == nil then + enqueueUrl( url, style, key ) + end + + -- Return cached URL + return cache[key] +end + +--- +-- Registers a style that can be used with `HTMLMaterial` +-- +-- @param name Style name. +-- @param params Table of style parameters. +-- +function AddHTMLMaterialStyle(name, params) + styles[name] = params +end diff --git a/lua/autorun/mediaplayer.lua b/lua/autorun/mediaplayer.lua new file mode 100644 index 0000000..c5a9e6b --- /dev/null +++ b/lua/autorun/mediaplayer.lua @@ -0,0 +1,81 @@ +local function LoadMediaPlayer() + print( "Loading 'mediaplayer' addon..." ) + + -- Check if MediaPlayer has already been loaded + if MediaPlayer then + MediaPlayer.__refresh = true + + -- HACK: Lua refresh fix; access local variable of baseclass lib + local _, BaseClassTable = debug.getupvalue(baseclass.Get, 1) + for classname, _ in pairs(BaseClassTable) do + if classname:find("mp_") then + BaseClassTable[classname] = nil + end + end + end + + -- shared includes + IncludeCS "includes/extensions/sh_file.lua" + IncludeCS "includes/extensions/sh_math.lua" + IncludeCS "includes/extensions/sh_table.lua" + IncludeCS "includes/extensions/sh_url.lua" + IncludeCS "includes/modules/EventEmitter.lua" + + if SERVER then + -- download clientside includes + AddCSLuaFile "includes/modules/browserpool.lua" + AddCSLuaFile "includes/modules/control.lua" + AddCSLuaFile "includes/modules/htmlmaterial.lua" + AddCSLuaFile "includes/extensions/cl_draw.lua" + + -- initialize serverside mediaplayer + include "mediaplayer/init.lua" + else + -- clientside includes + include "includes/modules/browserpool.lua" + include "includes/modules/control.lua" + include "includes/modules/htmlmaterial.lua" + include "includes/extensions/cl_draw.lua" + + -- initialize clientside mediaplayer + include "mediaplayer/cl_init.lua" + end + + -- Sandbox includes; these must always be included as the gamemode is still + -- set as 'base' when the addon is loading. Can't check if gamemode derives + -- Sandbox. + IncludeCS "menubar/mp_options.lua" + include "properties/mediaplayer.lua" + + if SERVER then + -- Reinstall media players on Lua refresh + for _, mp in pairs(MediaPlayer.GetAll()) do + if IsValid(mp.Entity) then + -- cache entity + local ent = mp.Entity + local queue = mp._Queue + local listeners = mp._Listeners + + -- remove media player + mp:Remove() + + -- install new media player + ent:InstallMediaPlayer() + + -- reinitialize settings + mp = ent._mp + -- mp._Queue = queue + + -- TODO: reapply listeners, for some reason the table is empty + -- after Lua refresh + mp:SetListeners( listeners ) + end + end + end +end + +-- First time load +LoadMediaPlayer() + +-- Fix for Lua refresh not always working... +hook.Add( "OnReloaded", "MediaPlayerRefresh", LoadMediaPlayer ) diff --git a/lua/autorun/mediaplayer/cl_cvars.lua b/lua/autorun/mediaplayer/cl_cvars.lua new file mode 100644 index 0000000..8e0c394 --- /dev/null +++ b/lua/autorun/mediaplayer/cl_cvars.lua @@ -0,0 +1,3 @@ +MediaPlayer.Cvars.Resolution = CreateClientConVar( "mediaplayer_resolution", 480, true, false ) +MediaPlayer.Cvars.Volume = CreateClientConVar( "mediaplayer_volume", 0.15, true, false ) +MediaPlayer.Cvars.Fullscreen = CreateClientConVar( "mediaplayer_fullscreen", 0, true, false ) \ No newline at end of file diff --git a/lua/autorun/mediaplayer/cl_idlescreen.lua b/lua/autorun/mediaplayer/cl_idlescreen.lua new file mode 100644 index 0000000..c749773 --- /dev/null +++ b/lua/autorun/mediaplayer/cl_idlescreen.lua @@ -0,0 +1,55 @@ +local DefaultIdlescreen = [[ + + + + + MediaPlayer Idlescreen + + + +

MediaPlayer Idlescreen

+

Right-click on the media player to see a list of available actions.

+ + +]] + +function MediaPlayer.GetIdlescreen() + + if not MediaPlayer._idlescreen then + local browser = vgui.Create( "DMediaPlayerHTML" ) + browser:SetPaintedManually(true) + browser:SetKeyBoardInputEnabled(false) + browser:SetMouseInputEnabled(false) + browser:SetPos(0,0) + + local resolution = MediaPlayer.Resolution() + browser:SetSize( resolution * 16/9, resolution ) + + -- TODO: set proper browser size + + MediaPlayer._idlescreen = browser + + local setup = hook.Run( "MediaPlayerSetupIdlescreen", browser ) + if not setup then + MediaPlayer._idlescreen:SetHTML( DefaultIdlescreen ) + end + end + + return MediaPlayer._idlescreen + +end \ No newline at end of file diff --git a/lua/autorun/mediaplayer/cl_init.lua b/lua/autorun/mediaplayer/cl_init.lua new file mode 100644 index 0000000..4c44a43 --- /dev/null +++ b/lua/autorun/mediaplayer/cl_init.lua @@ -0,0 +1,254 @@ +if MediaPlayer then + -- TODO: compare versions? + if MediaPlayer.__refresh then + MediaPlayer.__refresh = nil + else + return -- MediaPlayer has already been registered + end +end + +include "controls/dmediaplayerhtml.lua" +include "controls/dhtmlcontrols.lua" +include "controls/dmediaplayerrequest.lua" +include "shared.lua" +include "cl_idlescreen.lua" + +function MediaPlayer.Volume( volume ) + + if volume then + + -- Normalize volume + volume = volume > 1 and volume/100 or volume + + -- Set volume convar + RunConsoleCommand( "mediaplayer_volume", volume ) + + -- Apply volume to all media players + for _, mp in pairs(MediaPlayer.GetAll()) do + if mp:IsPlaying() then + local media = mp:CurrentMedia() + media:Volume( volume ) + end + end + + end + + return MediaPlayer.Cvars.Volume:GetFloat() + +end + +function MediaPlayer.Resolution( resolution ) + + if resolution then + resolution = math.Clamp( resolution, 16, 4096 ) + RunConsoleCommand( "mediaplayer_resolution", resolution ) + end + + return MediaPlayer.Cvars.Resolution:GetFloat() + +end + +-- TODO: Change to using a subscribe model rather than polling +function MediaPlayer.Poll( id ) + + net.Start( "MEDIAPLAYER.Update" ) + net.WriteString( id ) + net.SendToServer() + +end + +local function GetMediaPlayerId( obj ) + local mpId + + -- Determine mp parameter type and get the associated ID. + if isentity(obj) and obj.IsMediaPlayerEntity then + mpId = obj:GetMediaPlayerID() + -- elseif isentity(obj) and IsValid( obj:GetMediaPlayer() ) then + -- local mp = mp:GetMediaPlayer() + -- mpId = mp:GetId() + elseif istable(obj) and obj.IsMediaPlayer then + mpId = obj:GetId() + elseif isstring(obj) then + mpId = obj + else + return false -- Invalid parameters + end + + return mpId +end + +--- +-- Request to begin listening to a media player. +-- +-- @param Entity|Table|String Media player reference. +-- +function MediaPlayer.RequestListen( obj ) + + local mpId = GetMediaPlayerId(obj) + if not mpId then return end + + net.Start( "MEDIAPLAYER.RequestListen" ) + net.WriteString( mpId ) + net.SendToServer() + +end + +--- +-- Request a URL to be played on the given media player. +-- +-- @param Entity|Table|String Media player reference. +-- @param String Requested media URL. +-- +function MediaPlayer.Request( obj, url ) + + local mpId = GetMediaPlayerId( obj ) + if not mpId then return end + + if MediaPlayer.DEBUG then + print("MEDIAPLAYER.Request:", url, mpId) + end + + -- Verify valid URL as to not waste time networking + if not MediaPlayer.ValidUrl( url ) then + LocalPlayer():ChatPrint("The requested URL was invalid.") + return false + end + + local media = MediaPlayer.GetMediaForUrl( url ) + + local function request( err ) + if err then + -- TODO: don't use chatprint to notify the user + LocalPlayer():ChatPrint( "Request failed: " .. err ) + return + end + + net.Start( "MEDIAPLAYER.RequestMedia" ) + net.WriteString( mpId ) + net.WriteString( url ) + media:NetWriteRequest() -- send any additional data + net.SendToServer() + + if MediaPlayer.DEBUG then + print("MEDIAPLAYER.Request sent to server") + end + end + + -- Prepare any data prior to requesting if necessary + if media.PrefetchMetadata then + media:PreRequest(request) -- async + else + request() -- sync + end + +end + +function MediaPlayer.Pause( mp ) + + local mpId = GetMediaPlayerId( mp ) + if not mpId then return end + + net.Start( "MEDIAPLAYER.RequestPause" ) + net.WriteString( mpId ) + net.SendToServer() + +end + +function MediaPlayer.Skip( mp ) + + local mpId = GetMediaPlayerId( mp ) + if not mpId then return end + + net.Start( "MEDIAPLAYER.RequestSkip" ) + net.WriteString( mpId ) + net.SendToServer() + +end + +--- +-- Seek to a specific time in the current media. +-- +-- @param Entity|Table|String Media player reference. +-- @param String Seek time; HH:MM:SS +-- +function MediaPlayer.Seek( mp, time ) + + local mpId = GetMediaPlayerId( mp ) + if not mpId then return end + + net.Start( "MEDIAPLAYER.RequestSeek" ) + net.WriteString( mpId ) + net.WriteString( time ) + net.SendToServer() + +end + + +--[[--------------------------------------------------------- + Utility functions +-----------------------------------------------------------]] + +local FullscreenCvar = MediaPlayer.Cvars.Fullscreen + +function MediaPlayer.SetBrowserSize( browser, w, h ) + + local fullscreen = FullscreenCvar:GetBool() + + if fullscreen then + w, h = ScrW(), ScrH() + end + + browser:SetSize( w, h, fullscreen ) + +end + +function MediaPlayer.OpenRequestMenu( mp ) + + if ValidPanel(MediaPlayer._RequestMenu) then + return + end + + local req = vgui.Create( "MPRequestFrame" ) + req:SetMediaPlayer( mp ) + req:MakePopup() + req:Center() + + req.OnClose = function() + MediaPlayer._RequestMenu = nil + end + + MediaPlayer._RequestMenu = req + +end + +function MediaPlayer.MenuRequest( url ) + + local menu = MediaPlayer._RequestMenu + + if not ValidPanel(menu) then + return + end + + local mp = menu:GetMediaPlayer() + + menu:Close() + + MediaPlayer.Request( mp, url ) + +end + + +--[[--------------------------------------------------------- + Fonts +-----------------------------------------------------------]] + +local common = { + -- font = "Open Sans Condensed", + -- font = "Oswald", + font = "Clear Sans Medium", + antialias = true, + weight = 400 +} + +surface.CreateFont( "MediaTitle", table.Merge(common, { size = 72 }) ) +surface.CreateFont( "MediaRequestButton", table.Merge(common, { size = 26 }) ) diff --git a/lua/autorun/mediaplayer/config/server.lua b/lua/autorun/mediaplayer/config/server.lua new file mode 100644 index 0000000..8c4196b --- /dev/null +++ b/lua/autorun/mediaplayer/config/server.lua @@ -0,0 +1,28 @@ +--[[---------------------------------------------------------------------------- + The Media Player server configuration contains API keys used for requesting + metadata for various services. All keys provided with the addon should not + be used elsewhere as to respect data usage limits. +------------------------------------------------------------------------------]] +MediaPlayer.SetConfig({ + + --[[------------------------------------------------------------------------ + Google's Data API is used for YouTube and GoogleDrive requests. To + get your own API key, read through the following guide: + https://developers.google.com/youtube/v3/getting-started#intro + --------------------------------------------------------------------------]] + google = { + ["api_key"] = "AIzaSyAjSwUHzyoxhfQZmiSqoIBQpawm2ucF11E", + ["referrer"] = "http://mediaplayer.pixeltailgames.com/" + }, + + --[[------------------------------------------------------------------------ + SoundCloud API + + To register your own application, use the following webpage: + http://soundcloud.com/you/apps/new + --------------------------------------------------------------------------]] + soundcloud = { + ["client_id"] = "2e0e541854cbabd873d647c1d45f79e8" + } + +}) diff --git a/lua/autorun/mediaplayer/controls/dhtmlcontrols.lua b/lua/autorun/mediaplayer/controls/dhtmlcontrols.lua new file mode 100644 index 0000000..26f9ac8 --- /dev/null +++ b/lua/autorun/mediaplayer/controls/dhtmlcontrols.lua @@ -0,0 +1,349 @@ +--[[__ _ + / _| __ _ ___ ___ _ __ _ _ _ __ ___| |__ +| |_ / _` |/ __/ _ \ '_ \| | | | '_ \ / __| '_ \ +| _| (_| | (_| __/ |_) | |_| | | | | (__| | | | +|_| \__,_|\___\___| .__/ \__,_|_| |_|\___|_| |_| + |_| 2010 --]] + +--[[--------------------------------------------------------- + Browser controls +-----------------------------------------------------------]] + +local urllib = url + +local PANEL = {} + +AccessorFunc( PANEL, "HomeURL", "HomeURL", FORCE_STRING ) + +function PANEL:Init() + + local ButtonSize = 32 + local Margins = 2 + local Spacing = 0 + + self.BackButton = vgui.Create( "DImageButton", self ) + self.BackButton:SetSize( ButtonSize, ButtonSize ) + self.BackButton:SetMaterial( "gui/HTML/back" ) + self.BackButton:Dock( LEFT ) + self.BackButton:DockMargin( Spacing*3, Margins, Spacing, Margins ) + self.BackButton.DoClick = function() + self.BackButton:SetDisabled( true ) + self:HTMLBack() + self.Cur = self.Cur - 1 + self.Navigating = true + end + + self.ForwardButton = vgui.Create( "DImageButton", self ) + self.ForwardButton:SetSize( ButtonSize, ButtonSize ) + self.ForwardButton:SetMaterial( "gui/HTML/forward" ) + self.ForwardButton:Dock( LEFT ) + self.ForwardButton:DockMargin( Spacing, Margins, Spacing, Margins ) + self.ForwardButton.DoClick = function() + self.ForwardButton:SetDisabled( true ) + self:HTMLForward() + self.Cur = self.Cur + 1 + self.Navigating = true + end + + self.RefreshButton = vgui.Create( "MPRefreshButton", self ) + self.RefreshButton:SetSize( ButtonSize, ButtonSize ) + self.RefreshButton:Dock( LEFT ) + self.RefreshButton:DockMargin( Spacing, Margins, Spacing, Margins ) + self.RefreshButton.DoClick = function() + self.RefreshButton:SetDisabled( true ) + self.Refreshing = true + self.HTML:Refresh() + end + + self.HomeButton = vgui.Create( "DImageButton", self ) + self.HomeButton:SetSize( ButtonSize, ButtonSize ) + self.HomeButton:SetMaterial( "gui/HTML/home" ) + self.HomeButton:Dock( LEFT ) + self.HomeButton:DockMargin( Spacing, Margins, Spacing*3, Margins ) + self.HomeButton.DoClick = function() + self.HTML:Stop() + self.HTML:OpenURL( self:GetHomeURL() ) + end + + self.AddressBar = vgui.Create( "DTextEntry", self ) + self.AddressBar:Dock( FILL ) + self.AddressBar:DockMargin( Spacing, Margins * 3, Spacing, Margins * 3 ) + self.AddressBar.OnEnter = function() + self.HTML:Stop() + self.HTML:OpenURL( self.AddressBar:GetValue() ) + end + + local AddrSetText = self.AddressBar.SetText + self.AddressBar.SetText = function (panel, text) + AddrSetText( panel, urllib.unescape(text) ) + end + + self.RequestButton = vgui.Create( "MPRequestButton", self ) + self.RequestButton:SetDisabled( true ) + self.RequestButton:Dock( RIGHT ) + self.RequestButton:DockMargin( 8, 4, 8, 4 ) + self.RequestButton.DoClick = function() + MediaPlayer.MenuRequest( self.HTML:GetURL() ) + end + + self:SetHeight( ButtonSize + Margins * 2 ) + + self.NavStack = 0 + self.History = {} + self.Cur = 1 + + -- This is the default look, feel free to change it on your created control :) + self:SetButtonColor( Color( 250, 250, 250, 200 ) ) + self.BorderSize = 4 + self.BackgroundColor = Color( 33, 33, 33, 255 ) + self:SetHomeURL( "http://www.google.com" ) + +end + +function PANEL:SetHTML( html ) + + self.HTML = html + + if ( html.URL ) then + self:SetHomeURL( self.HTML.URL ) + end + + self.RefreshButton:SetHTML(html) + self.AddressBar:SetText( self:GetHomeURL() ) + self:UpdateHistory( self:GetHomeURL() ) + + local OldFunc = self.HTML.OpeningURL + self.HTML.OpeningURL = function( panel, url, target, postdata, bredirect ) + + self.NavStack = self.NavStack + 1 + self.AddressBar:SetText( url ) + self:StartedLoading() + + if ( OldFunc ) then + OldFunc( panel, url, target, postdata, bredirect ) + end + + self:UpdateHistory( url ) + + end + + local OldFunc = self.HTML.FinishedURL + self.HTML.FinishedURL = function( panel, url ) + + self.AddressBar:SetText( url ) + self:FinishedLoading() + + -- Check for valid URL + if MediaPlayer.ValidUrl( url ) then + self.RequestButton:SetDisabled( false ) + else + self.RequestButton:SetDisabled( true ) + end + + if ( OldFunc ) then + OldFunc( panel, url ) + end + + end + +end + +function PANEL:UpdateHistory( url ) + + --print( "PANEL:UpdateHistory", url ) + self.Cur = math.Clamp( self.Cur, 1, table.Count( self.History ) ) + + local top = self.History[self.Cur] + + -- Ignore page refresh + if top == url then + return + end + + if ( self.Refreshing ) then + + self.Refreshing = false + self.RefreshButton:SetDisabled( false ) + return + + end + + if ( self.Navigating ) then + + self.Navigating = false + self:UpdateNavButtonStatus() + return + + end + + -- We were back in the history queue, but now we're navigating + -- So clear the front out so we can re-write history!! + if ( self.Cur < table.Count( self.History ) ) then + + for i = self.Cur+1, table.Count( self.History ) do + self.History[i] = nil + end + + end + + self.Cur = table.insert( self.History, url ) + + self:UpdateNavButtonStatus() + +end + +function PANEL:HTMLBack() + if self.Cur <= 1 then return end + self.Cur = self.Cur - 1 + self.HTML:OpenURL( self.History[ self.Cur ], true ) +end + +function PANEL:HTMLForward() + if self.Cur == #self.History then return end + self.Cur = self.Cur + 1 + self.HTML:OpenURL( self.History[ self.Cur ], true ) +end + +function PANEL:FinishedLoading() + + self.RefreshButton:SetDisabled( false ) + +end + +function PANEL:StartedLoading() + + self.RefreshButton:SetDisabled( true ) + +end + +function PANEL:UpdateNavButtonStatus() + + --print( self.Cur, table.Count( self.History ) ) + + self.ForwardButton:SetDisabled( self.Cur >= table.Count( self.History ) ) + self.BackButton:SetDisabled( self.Cur == 1 ) + +end + +function PANEL:SetButtonColor( col ) + + self.BackButton:SetColor( col ) + self.ForwardButton:SetColor( col ) + self.RefreshButton:SetColor( col ) + self.HomeButton:SetColor( col ) + +end + +function PANEL:Paint() + + draw.RoundedBoxEx( self.BorderSize, 0, 0, self:GetWide(), self:GetTall(), self.BackgroundColor, true, true, false, false ) + +end + +derma.DefineControl( "MPHTMLControls", "", PANEL, "Panel" ) + + +--[[--------------------------------------------------------- + Media request button + Embedded inside of the browser controls. +-----------------------------------------------------------]] + +local RequestButton = {} + +-- RequestButton.DisabledColor = Color(189, 195, 199) +-- RequestButton.DepressedColor = Color(192, 57, 43) +RequestButton.HoverColor = Color(192, 57, 43) +RequestButton.DefaultColor = Color(231, 76, 60) +RequestButton.DisabledColor = RequestButton.DefaultColor +RequestButton.DepressedColor = RequestButton.DefaultColor + +RequestButton.DefaultTextColor = Color(236, 236, 236) +RequestButton.DisabledTextColor = Color(158, 48, 36) + +function RequestButton:Init() + DButton.Init(self) + + local ButtonSize = 32 + + self:SetSize( ButtonSize*8, ButtonSize ) + self:SetFont( "MediaRequestButton" ) + + self:SetDisabled( true ) +end + +function RequestButton:SetDisabled( disabled ) + if disabled then + self:SetText( "FIND A VIDEO" ) + else + self:SetText( "REQUEST URL" ) + end + + DButton.SetDisabled( self, disabled ) +end + +function RequestButton:UpdateColours() + if self:GetDisabled() then + return self:SetTextStyleColor( self.DisabledTextColor ) + else + return self:SetTextStyleColor( self.DefaultTextColor ) + end +end + +function RequestButton:Paint( w, h ) + local col + + if self:GetDisabled() then + col = self.DisabledColor + elseif self.Depressed or self.m_bSelected then + col = self.DepressedColor + elseif self:IsHovered() then + col = self.HoverColor + else + -- Pulse effect + local h, s, v = ColorToHSV( self.DefaultColor ) + v = 0.7 + math.sin(RealTime() * 10) * 0.3 + + col = HSVToColor(h,s,v) + end + + draw.RoundedBox( 2, 0, 0, w, h, col ) +end + +derma.DefineControl( "MPRequestButton", "", RequestButton, "DButton" ) + + +--[[--------------------------------------------------------- + Media refresh button + Embedded inside of the browser controls. +-----------------------------------------------------------]] + +local RefreshButton = {} + +AccessorFunc( RefreshButton, "HTML", "HTML" ) + +local LoadingTexture = surface.GetTextureID("gui/html/refresh") + +function RefreshButton:Init() + DButton.Init(self) + + self:SetText( "" ) + self.TextureColor = Color(255,255,255,255) +end + +function RefreshButton:SetColor( color ) + self.TextureColor = color +end + +function RefreshButton:Paint( w, h ) + local ang = 0 + + if ValidPanel(self.HTML) and self.HTML:IsLoading() then + ang = RealTime() * -512 + end + + surface.SetDrawColor(self.TextureColor) + surface.SetTexture(LoadingTexture) + surface.DrawTexturedRectRotated( w/2, h/2, w, h, ang ) +end + +derma.DefineControl( "MPRefreshButton", "", RefreshButton, "DButton" ) diff --git a/lua/autorun/mediaplayer/controls/dmediaplayerhtml.lua b/lua/autorun/mediaplayer/controls/dmediaplayerhtml.lua new file mode 100644 index 0000000..2303238 --- /dev/null +++ b/lua/autorun/mediaplayer/controls/dmediaplayerhtml.lua @@ -0,0 +1,390 @@ +local PANEL = {} + +DEFINE_BASECLASS( "Panel" ) + +local JS_CallbackHack = [[(function(){ + var funcname = '%s'; + window[funcname] = function(){ + _gm[funcname].apply(_gm,arguments); + } +})();]] + +--[[--------------------------------------------------------- + +-----------------------------------------------------------]] + +function PANEL:Init() + + self.JS = {} + self.Callbacks = {} + + self.URL = "about:blank" + + -- + -- Implement a console - because awesomium doesn't provide it for us anymore + -- + local console_funcs = {'log','error','debug','warn','info'} + for _, func in pairs(console_funcs) do + self:AddFunction( "console", func, function(param) + self:ConsoleMessage( param, func ) + end ) + end + + self:AddFunction( "gmod", "getUrl", function( url, finished ) + self.URL = url + + if finished then + self:FinishedURL( url ) + else + self:OpeningURL( url ) + end + end ) + +end + +function PANEL:Think() + + if self:IsLoading() then + + -- Call started loading + if not self._loading then + + -- Get the page URL + self:RunJavascript("gmod.getUrl(window.location.href, false);") + + self._loading = true + self:OnStartLoading() + + end + + else + + -- Call finished loading + if self._loading then + + -- Get the page URL + self:RunJavascript("gmod.getUrl(window.location.href, true);") + + -- Hack to add window object callbacks + if self.Callbacks.window then + for funcname, callback in pairs(self.Callbacks.window) do + self:RunJavascript( JS_CallbackHack:format(funcname) ) + end + end + + self._loading = nil + self:OnFinishLoading() + + end + + -- Run queued javascript + if self.JS then + for k, v in pairs( self.JS ) do + self:RunJavascript( v ) + end + self.JS = nil + end + + end + +end + +function PANEL:GetURL() + return self.URL +end + +function PANEL:SetURL( url ) + local current = self.URL + + if current ~= url then + self:OnURLChanged( url, current ) + end + + self.URL = url +end + +function PANEL:OnURLChanged( new, old ) + +end + + +--[[--------------------------------------------------------- + Awesomium Override Functions +-----------------------------------------------------------]] + +function PANEL:SetSize( w, h, fullscreen ) + + if fullscreen then + + -- Cache fullscreen size + local cw, ch = self:GetSize() + self._OrigSize = { w = cw, h = ch } + + -- Render before the HUD + self:ParentToHUD() + + elseif self._OrigSize then + + -- Restore cached size + w = self._OrigSize.w + h = self._OrigSize.h + self._OrigSize = nil + + -- Reparent due to hud parented panels sometimes being inaccessible + -- from Lua. + self:SetParent( vgui.GetWorldPanel() ) + + else + self._OrigSize = nil + end + + if not (w and h) then return end + + BaseClass.SetSize( self, w, h ) + +end + +function PANEL:OpenURL( url ) + + if MediaPlayer.DEBUG then + print("DMediaPlayerHTML.OpenURL", url) + end + + self:SetURL( url ) + + BaseClass.OpenURL( self, url ) + +end + +function PANEL:SetHTML( html ) + + if MediaPlayer.DEBUG then + print("DMediaPlayerHTML.SetHTML") + print(html) + end + + BaseClass.SetHTML( self, html ) + +end + +--[[function PANEL:RunJavascript( js ) + + if MediaPlayer.DEBUG then + print("DMediaPlayerHTML.RunJavascript", js) + end + + BaseClass.RunJavascript( self, js ) + +end]] + + +--[[--------------------------------------------------------- + Window loading events +-----------------------------------------------------------]] + +-- +-- Called when the page begins loading +-- +function PANEL:OnStartLoading() + +end + +-- +-- Called when the page finishes loading all assets +-- +function PANEL:OnFinishLoading() + +end + + +--[[--------------------------------------------------------- + Lua => JavaScript queue + + This code only runs when the page is finished loading; + this means all assets (images, CSS, etc.) must load first! +-----------------------------------------------------------]] + +function PANEL:QueueJavascript( js ) + + -- + -- Can skip using the queue if there's nothing else in it + -- + if not ( self.JS or self:IsLoading() ) then + return self:RunJavascript( js ) + end + + self.JS = self.JS or {} + + table.insert( self.JS, js ) + self:Think() + +end + +PANEL.QueueJavaScript = PANEL.QueueJavascript +PANEL.Call = PANEL.QueueJavascript + + +--[[--------------------------------------------------------- + Handle console logging from JavaScript +-----------------------------------------------------------]] + +PANEL.ConsoleColors = { + ["default"] = Color(255,160,255), + ["text"] = Color(255,255,255), + ["error"] = Color(235,57,65), + ["warn"] = Color(227,181,23), + ["info"] = Color(100,173,229), +} + +function PANEL:ConsoleMessage( ... ) + + local args = {...} + local msg = args[1] + + -- Three arguments are passed in if an error occured + if #args == 3 then + + local script = args[2] + local linenum = args[3] + local col = self.ConsoleColors.error + + local out = { + "[JavaScript]", + msg, + ",", + script, + ":", + linenum, + "\n" + } + + MsgC( col, table.concat(out, " ") ) + + else + + local func = args[2] + + if not isstring( msg ) then + msg = "*js variable* (" .. type(msg) .. ": " .. tostring(msg) .. ")" + end + + -- Run Lua from JavaScript console logging (POTENTIALLY HARMFUL!) + --[[if msg:StartWith( "RUNLUA:" ) then + local strLua = msg:sub( 8 ) + + SELF = self + RunString( strLua ) + SELF = nil + + return + end]] + + -- Play a sound from JavaScript console logging + if msg:StartWith( "PLAY:" ) then + local soundpath = msg:sub( 7 ) + surface.PlaySound( soundpath ) + return + end + + -- Output console message with prefix + local prefixColor = self.ConsoleColors.default + local prefix = "[HTML" + if func and func:len() > 0 and func ~= "log" then + if self.ConsoleColors[func] then + prefixColor = self.ConsoleColors[func] + end + prefix = prefix .. ":" .. func:upper() + end + prefix = prefix .. "] " + + MsgC( prefixColor, prefix ) + MsgC( self.ConsoleColors.text, msg, "\n" ) + + end + +end + + +--[[--------------------------------------------------------- + JavaScript callbacks +-----------------------------------------------------------]] + +local JSObjects = { + window = "_gm", + this = "_gm", + _gm = "window" +} + +-- +-- Called by the engine when a callback function is called +-- +function PANEL:OnCallback( obj, func, args ) + + -- Hack for adding window callbacks + obj = JSObjects[obj] or obj + + if not self.Callbacks[ obj ] then return end + + -- + -- Use AddFunction to add functions to this. + -- + local f = self.Callbacks[ obj ][ func ] + + if ( f ) then + return f( unpack( args ) ) + end + +end + +-- +-- Add a function to Javascript +-- +function PANEL:AddFunction( obj, funcname, func ) + + -- Hack for adding window callbacks + -- obj = JSObjects[obj] or obj + + if obj == "this" then + obj = "window" + end + + -- Create the `object` if it doesn't exist + if not self.Callbacks[ obj ] then + self:NewObject( obj ) + self.Callbacks[ obj ] = {} + end + + -- This creates the function in javascript (which redirects to c++ which calls OnCallback here) + self:NewObjectCallback( JSObjects[obj] or obj, funcname ) + + -- Store the function so OnCallback can find it and call it + self.Callbacks[ obj ][ funcname ] = func + +end + + +--[[--------------------------------------------------------- + Remove Scrollbars +-----------------------------------------------------------]] + +-- TODO: Change to appending CSS style? +-- ::-webkit-scrollbar { visibility: hidden; } + +local JS_RemoveScrollbars = "document.body.style.overflow = 'hidden';" + +function PANEL:RemoveScrollbars() + self:QueueJavascript(JS_RemoveScrollbars) +end + + +--[[--------------------------------------------------------- + Compatibility functions +-----------------------------------------------------------]] + +function PANEL:OpeningURL( url ) +end + +function PANEL:FinishedURL( url ) +end + +derma.DefineControl( "DMediaPlayerHTML", "", PANEL, "Awesomium" ) diff --git a/lua/autorun/mediaplayer/controls/dmediaplayerrequest.lua b/lua/autorun/mediaplayer/controls/dmediaplayerrequest.lua new file mode 100644 index 0000000..2705d7e --- /dev/null +++ b/lua/autorun/mediaplayer/controls/dmediaplayerrequest.lua @@ -0,0 +1,128 @@ +local PANEL = {} +PANEL.HistoryWidth = 300 +PANEL.BackgroundColor = Color(22, 22, 22) + +local CloseTexture = Material( "theater/close.png" ) + +AccessorFunc( PANEL, '_mp', 'MediaPlayer' ) + +function PANEL:Init() + + self:SetPaintBackgroundEnabled( true ) + self:SetFocusTopLevel( true ) + + local w = math.Clamp( ScrW() - 100, 800, 1152 + self.HistoryWidth ) + local h = ScrH() + if h > 800 then + h = h * 3/4 + elseif h > 600 then + h = h * 7/8 + end + self:SetSize( w, h ) + + self.CloseButton = vgui.Create( "DButton", self ) + self.CloseButton:SetZPos( 5 ) + self.CloseButton:NoClipping( true ) + self.CloseButton:SetText( "" ) + self.CloseButton.DoClick = function ( button ) + self:Close() + end + self.CloseButton.Paint = function( panel, w, h ) + DisableClipping( true ) + + surface.SetDrawColor( 48, 55, 71 ) + surface.DrawRect( 2, 2, w - 4, h - 4 ) + + surface.SetDrawColor( 26, 30, 38 ) + surface.SetMaterial( CloseTexture ) + surface.DrawTexturedRect( 0, 0, w, h ) + + DisableClipping( false ) + end + + self.BrowserContainer = vgui.Create( "DPanel", self ) + + self.Browser = vgui.Create( "DMediaPlayerHTML", self.BrowserContainer ) + self.Browser:AddFunction( "gmod", "requestUrl", function (url) + MediaPlayer.MenuRequest( url ) + self:Close() + end ) + + self.Browser:OpenURL( "http://gmtower.org/apps/mediaplayer/" ) + + self.Controls = vgui.Create( "MPHTMLControls", self.BrowserContainer ) + self.Controls:SetHTML( self.Browser ) + self.Controls.BorderSize = 0 + + -- Listen for all mouse press events + hook.Add( "GUIMousePressed", self, self.OnGUIMousePressed ) + +end + +function PANEL:Paint( w, h ) + + -- Draw background for fully transparent webpages + surface.SetDrawColor( self.BackgroundColor ) + surface.DrawRect( 0, 0, w, h ) + + return true + +end + +function PANEL:OnRemove() + hook.Remove( "GUIMousePressed", self ) +end + +function PANEL:Close() + if ValidPanel(self.Browser) then + self.Browser:Remove() + end + + self:OnClose() + self:Remove() +end + +function PANEL:OnClose() + +end + +function PANEL:CheckClose() + + local x, y = self:CursorPos() + + -- Remove panel if mouse is clicked outside of itself + if not (gui.IsGameUIVisible() or gui.IsConsoleVisible()) and + ( x < 0 or x > self:GetWide() or y < 0 or y > self:GetTall() ) then + self:Close() + end + +end + +function PANEL:PerformLayout() + + local w, h = self:GetSize() + + self.CloseButton:SetSize( 32, 32 ) + self.CloseButton:SetPos( w - 34, 2 ) + + self.BrowserContainer:Dock( FILL ) + + self.Browser:Dock( FILL ) + + self.Controls:Dock( TOP ) + self.Controls:DockPadding( 0, 0, 32, 0 ) + +end + +--- +-- Close the panel when the mouse has been pressed outside of the panel. +-- +function PANEL:OnGUIMousePressed( key ) + + if key == MOUSE_LEFT then + self:CheckClose() + end + +end + +vgui.Register( "MPRequestFrame", PANEL, "EditablePanel" ) diff --git a/lua/autorun/mediaplayer/init.lua b/lua/autorun/mediaplayer/init.lua new file mode 100644 index 0000000..ee0d8fc --- /dev/null +++ b/lua/autorun/mediaplayer/init.lua @@ -0,0 +1,160 @@ +if MediaPlayer then + -- TODO: compare versions? + if MediaPlayer.__refresh then + MediaPlayer.__refresh = nil + else + return -- MediaPlayer has already been registered + end +end + +resource.AddFile "resource/fonts/ClearSans-Medium.ttf" + +AddCSLuaFile "controls/dmediaplayerhtml.lua" +AddCSLuaFile "controls/dhtmlcontrols.lua" +AddCSLuaFile "controls/dmediaplayerrequest.lua" +AddCSLuaFile "cl_init.lua" +AddCSLuaFile "cl_idlescreen.lua" +AddCSLuaFile "shared.lua" +AddCSLuaFile "sh_mediaplayer.lua" +AddCSLuaFile "sh_services.lua" +AddCSLuaFile "sh_history.lua" + +include "shared.lua" +include "sv_metadata.lua" + +local function NetReadMediaPlayer() + + local mpId = net.ReadString() + local mp = MediaPlayer.GetById(mpId) + + if not IsValid(mp) then + if MediaPlayer.DEBUG then + print("MEDIAPLAYER.Request: Invalid media player ID", mpId, mp, ply) + end + return false + end + + return mp + +end + +--[[--------------------------------------------------------- + Request +-----------------------------------------------------------]] + +util.AddNetworkString( "MEDIAPLAYER.RequestListen" ) +util.AddNetworkString( "MEDIAPLAYER.RequestMedia" ) +util.AddNetworkString( "MEDIAPLAYER.RequestPause" ) +util.AddNetworkString( "MEDIAPLAYER.RequestSkip" ) +util.AddNetworkString( "MEDIAPLAYER.RequestSeek" ) + +local ListenDelay = 0.5 -- seconds + +local function OnListenRequest( len, ply ) + + if not IsValid(ply) then return end + + if ply._NextListen and ply._NextListen > CurTime() then + return + end + + local mpId = net.ReadString() + local mp = MediaPlayer.GetById(mpId) + if not IsValid(mp) then return end + + if MediaPlayer.DEBUG then + print("MEDIAPLAYER.RequestListen:", mpId, ply) + end + + -- TODO: check if listener can actually be a listener + if mp:HasListener(ply) then + mp:RemoveListener(ply) + else + mp:AddListener(ply) + end + + ply._NextListen = CurTime() + ListenDelay + +end +net.Receive( "MEDIAPLAYER.RequestListen", OnListenRequest ) + +local function OnMediaRequest( len, ply ) + + if not IsValid(ply) then return end + + -- TODO: impose request delay for player + + local mp = NetReadMediaPlayer() + if not mp then return end + + local url = net.ReadString() + + if MediaPlayer.DEBUG then + print("MEDIAPLAYER.RequestMedia:", url, mp:GetId(), ply) + end + + -- Validate the URL + if not MediaPlayer.ValidUrl( url ) then + ply:ChatPrint( "The requested URL wasn't valid." ) + return + end + + -- Build the media object for the URL + local media = MediaPlayer.GetMediaForUrl( url ) + media:NetReadRequest() + + mp:RequestMedia( media, ply ) + +end +net.Receive( "MEDIAPLAYER.RequestMedia", OnMediaRequest ) + +local function OnPauseMedia( len, ply ) + + if not IsValid(ply) then return end + + local mp = NetReadMediaPlayer() + if not mp then return end + + if MediaPlayer.DEBUG then + print("MEDIAPLAYER.RequestPause:", mp:GetId(), ply) + end + + mp:RequestPause( ply ) + +end +net.Receive( "MEDIAPLAYER.RequestPause", OnPauseMedia ) + +local function OnSkipMedia( len, ply ) + + if not IsValid(ply) then return end + + local mp = NetReadMediaPlayer() + if not mp then return end + + if MediaPlayer.DEBUG then + print("MEDIAPLAYER.RequestSkip:", mp:GetId(), ply) + end + + mp:RequestSkip( ply ) + +end +net.Receive( "MEDIAPLAYER.RequestSkip", OnSkipMedia ) + + +local function OnSeekMedia( len, ply ) + + if not IsValid(ply) then return end + + local mp = NetReadMediaPlayer() + if not mp then return end + + if MediaPlayer.DEBUG then + print("MEDIAPLAYER.RequestSeek:", mp:GetId(), ply) + end + + local seekTime = net.ReadString() + + mp:RequestSeek( ply, seekTime ) + +end +net.Receive( "MEDIAPLAYER.RequestSeek", OnSeekMedia ) diff --git a/lua/autorun/mediaplayer/players/_mixins/vote.lua b/lua/autorun/mediaplayer/players/_mixins/vote.lua new file mode 100644 index 0000000..6f96341 --- /dev/null +++ b/lua/autorun/mediaplayer/players/_mixins/vote.lua @@ -0,0 +1,15 @@ +-- TODO: mixins will be used for adding common functionality to mediaplayer +-- types. In this case, voting functionality for items in the media queue. + +local VOTE = {} + +function VOTE:New( ply, value ) + local obj = setmetatable({}, self) + + self.Player = ply + self.value = value + + return obj +end + +function MEDIAPLAYER: diff --git a/lua/autorun/mediaplayer/players/base/cl_draw.lua b/lua/autorun/mediaplayer/players/base/cl_draw.lua new file mode 100644 index 0000000..aae2c74 --- /dev/null +++ b/lua/autorun/mediaplayer/players/base/cl_draw.lua @@ -0,0 +1,171 @@ +local pcall = pcall +local Color = Color +local RealTime = RealTime +local ValidPanel = ValidPanel +local Vector = Vector +local cam = cam +local draw = draw +local math = math +local string = string +local surface = surface + +local TEXT_ALIGN_CENTER = draw.TEXT_ALIGN_CENTER +local TEXT_ALIGN_TOP = draw.TEXT_ALIGN_TOP +local TEXT_ALIGN_BOTTOM = draw.TEXT_ALIGN_BOTTOM +local TEXT_ALIGN_LEFT = draw.TEXT_ALIGN_LEFT +local TEXT_ALIGN_RIGHT = draw.TEXT_ALIGN_RIGHT + +local TextPaddingX = 12 +local TextPaddingY = 12 + +local TextBoxPaddingX = 8 +local TextBoxPaddingY = 2 + +local TextBgColor = Color(0, 0, 0, 200) +local BarBgColor = Color(0, 0, 0, 200) +local BarFgColor = Color(255, 255, 255, 255) + +local function DrawText( text, font, x, y, xalign, yalign ) + return draw.SimpleText( text, font, x, y, color_white, xalign, yalign ) +end + +local function DrawTextBox( text, font, x, y, xalign, yalign ) + + xalign = xalign or TEXT_ALIGN_LEFT + yalign = yalign or TEXT_ALIGN_TOP + + surface.SetFont( font ) + tw, th = surface.GetTextSize( text ) + + if xalign == TEXT_ALIGN_CENTER then + x = x - tw/2 + elseif xalign == TEXT_ALIGN_RIGHT then + x = x - tw + end + + if yalign == TEXT_ALIGN_CENTER then + y = y - th/2 + elseif yalign == TEXT_ALIGN_BOTTOM then + y = y - th + end + + surface.SetDrawColor( TextBgColor ) + surface.DrawRect( x, y, + tw + TextBoxPaddingX * 2, + th + TextBoxPaddingY * 2 ) + +end + +local UTF8SubLastCharPattern = "[^\128-\191][\128-\191]*$" +local OverflowString = "..." -- ellipsis + +--- +-- Limits a rendered string's width based on a maximum width. +-- +-- @param text Text string. +-- @param font Font. +-- @param w Maximum width. +-- @return String String fitting the maximum required width. +-- +local function RestrictStringWidth( text, font, w ) + + -- TODO: Cache this + + surface.SetFont( font ) + local curwidth = surface.GetTextSize( text ) + local overflow = false + + -- Reduce text by one character until it fits + while curwidth > w do + + -- Text has overflowed, append overflow string on return + if not overflow then + overflow = true + end + + -- Cut off last character + text = string.gsub(text, UTF8SubLastCharPattern, "") + + -- Check size again + curwidth = surface.GetTextSize( text .. OverflowString ) + + end + + return overflow and (text .. OverflowString) or text + +end + +function MEDIAPLAYER:DrawHTML( browser, w, h ) + surface.SetDrawColor( 0, 0, 0, 255 ) + surface.DrawRect( 0, 0, w, h ) + draw.HTMLTexture( browser, w, h ) +end + +function MEDIAPLAYER:DrawMediaInfo( media, w, h ) + + -- TODO: Fadeout media info instead of just hiding + if not vgui.CursorVisible() and RealTime() - self._LastMediaUpdate > 3 then + return + end + + -- Text dimensions + local tw, th + + -- Title background + local titleStr = RestrictStringWidth( media:Title(), "MediaTitle", + w - (TextPaddingX * 2 + TextBoxPaddingX * 2) ) + + DrawTextBox( titleStr, "MediaTitle", TextPaddingX, TextPaddingY ) + + -- Title + DrawText( titleStr, "MediaTitle", + TextPaddingX + TextBoxPaddingX, + TextPaddingY + TextBoxPaddingY ) + + -- Track bar + if media:IsTimed() then + + local duration = media:Duration() + local curTime = media:CurrentTime() + local percent = math.Clamp( curTime / duration, 0, 1 ) + + -- Bar height + local bh = math.Round(h * 1/32) + + -- Bar background + draw.RoundedBox( 0, 0, h - bh, w, bh, BarBgColor ) + + -- Bar foreground (progress) + draw.RoundedBox( 0, 0, h - bh, w * percent, bh, BarFgColor ) + + local timeY = h - bh - TextPaddingY * 2 + + -- Current time + local curTimeStr = string.FormatSeconds(math.Clamp(math.Round(curTime), 0, duration)) + + DrawTextBox( curTimeStr, "MediaTitle", TextPaddingX, timeY, + TEXT_ALIGN_LEFT, TEXT_ALIGN_BOTTOM ) + DrawText( curTimeStr, "MediaTitle", TextPaddingX * 2, timeY, + TEXT_ALIGN_LEFT, TEXT_ALIGN_BOTTOM ) + + -- Duration + local durationStr = string.FormatSeconds( duration ) + + DrawTextBox( durationStr, "MediaTitle", w - TextPaddingX * 2, timeY, + TEXT_ALIGN_RIGHT, TEXT_ALIGN_BOTTOM ) + DrawText( durationStr, "MediaTitle", w - TextBoxPaddingX * 2, timeY, + TEXT_ALIGN_RIGHT, TEXT_ALIGN_BOTTOM ) + + end + + -- Volume + local volume = MediaPlayer.Volume() + local volumeStr = tostring( math.Round( volume * 100 ) ) + + -- DrawText( volumeStr, "MediaTitle", w - TextPaddingX, h/2, + -- TEXT_ALIGN_CENTER ) + + + -- Loading indicator + +end diff --git a/lua/autorun/mediaplayer/players/base/cl_fullscreen.lua b/lua/autorun/mediaplayer/players/base/cl_fullscreen.lua new file mode 100644 index 0000000..a69eec8 --- /dev/null +++ b/lua/autorun/mediaplayer/players/base/cl_fullscreen.lua @@ -0,0 +1,78 @@ +local pcall = pcall +local Color = Color +local RealTime = RealTime +local ScrW = ScrW +local ScrH = ScrH +local ValidPanel = ValidPanel +local Vector = Vector +local cam = cam +local draw = draw +local math = math +local string = string +local surface = surface + +local FullscreenCvar = MediaPlayer.Cvars.Fullscreen + +--[[--------------------------------------------------------- + Convar callback +-----------------------------------------------------------]] + +local function OnFullscreenConVarChanged( name, old, new ) + + local media + + for _, mp in pairs(MediaPlayer.List) do + + mp._LastMediaUpdate = RealTime() + + media = mp:CurrentMedia() + + if IsValid(media) and ValidPanel(media.Browser) then + MediaPlayer.SetBrowserSize( media.Browser ) + end + + end + + MediaPlayer.SetBrowserSize( MediaPlayer.GetIdlescreen() ) + +end +cvars.AddChangeCallback( FullscreenCvar:GetName(), OnFullscreenConVarChanged ) + + +--[[--------------------------------------------------------- + Draw functions +-----------------------------------------------------------]] + +function MEDIAPLAYER:DrawFullscreen() + + -- Don't draw if we're not fullscreen + if not FullscreenCvar:GetBool() then return end + + local w, h = ScrW(), ScrH() + local media = self:CurrentMedia() + + if IsValid(media) then + + -- Custom media draw function + if media.Draw then + media:Draw( w, h ) + end + -- TODO: else draw 'not yet implemented' screen? + + -- Draw media info + local succ, err = pcall( self.DrawMediaInfo, self, media, w, h ) + if not succ then + print( err ) + end + + else + + local browser = MediaPlayer.GetIdlescreen() + + if ValidPanel(browser) then + self:DrawHTML( browser, w, h ) + end + + end + +end diff --git a/lua/autorun/mediaplayer/players/base/cl_init.lua b/lua/autorun/mediaplayer/players/base/cl_init.lua new file mode 100644 index 0000000..44c20a0 --- /dev/null +++ b/lua/autorun/mediaplayer/players/base/cl_init.lua @@ -0,0 +1,140 @@ +include "shared.lua" +include "cl_draw.lua" +include "cl_fullscreen.lua" +include "cl_net.lua" + +function MEDIAPLAYER:NetReadUpdate() + -- Allows for another media player type to extend update net messages +end + +function MEDIAPLAYER:OnQueueKeyPressed( down, held ) + self._LastMediaUpdate = RealTime() +end + +local function OnMediaUpdate( len ) + + local mpId = net.ReadString() + local mpType = net.ReadString() + + if MediaPlayer.DEBUG then + print( "Received MEDIAPLAYER.Update", mpId, mpType ) + end + + local mp = MediaPlayer.GetById(mpId) + if not mp then + mp = MediaPlayer.Create( mpId, mpType ) + end + + local state = mp.net.ReadPlayerState() + + -- Read extended update information + mp:NetReadUpdate() + + -- Clear old queue + mp:ClearMediaQueue() + + -- Read queue information + local count = net.ReadUInt( math.CeilPower2(mp.MaxMediaItems)/2 ) + for i = 1, count do + local media = mp.net.ReadMedia() + mp:AddMedia(media) + end + + mp:SetPlayerState( state ) + + hook.Call( "OnMediaPlayerUpdate", mp ) + +end +net.Receive( "MEDIAPLAYER.Update", OnMediaUpdate ) + +local function OnMediaSet( len ) + + if MediaPlayer.DEBUG then + print( "Received MEDIAPLAYER.Media" ) + end + + local mpId = net.ReadString() + local mp = MediaPlayer.GetById(mpId) + + if not mp then + if MediaPlayer.DEBUG then + ErrorNoHalt("Received media for invalid mediaplayer\n") + print("ID: " .. tostring(mpId)) + debug.Trace() + end + return + end + + if mp:GetPlayerState() >= MP_STATE_PLAYING then + mp:OnMediaFinished() + end + + local media = mp.net.ReadMedia() + + if media then + local startTime = net.ReadInt(32) + media:StartTime( startTime ) + + local state = mp:GetPlayerState() + + if state == MP_STATE_PLAYING then + media:Play() + else + media:Pause() + end + end + + mp:SetMedia( media ) + +end +net.Receive( "MEDIAPLAYER.Media", OnMediaSet ) + +local function OnMediaRemoved( len ) + + if MediaPlayer.DEBUG then + print( "Received MEDIAPLAYER.Remove" ) + end + + local mpId = net.ReadString() + local mp = MediaPlayer.GetById(mpId) + if not mp then return end + + mp:Remove() + +end +net.Receive( "MEDIAPLAYER.Remove", OnMediaRemoved ) + +local function OnMediaSeek( len ) + + local mpId = net.ReadString() + local mp = MediaPlayer.GetById(mpId) + if not ( mp and (mp:GetPlayerState() >= MP_STATE_PLAYING) ) then return end + + local startTime = net.ReadInt(32) + + if MediaPlayer.DEBUG then + print( "Received MEDIAPLAYER.Seek", mpId, startTime ) + end + + local media = mp:CurrentMedia() + media:StartTime( startTime ) + +end +net.Receive( "MEDIAPLAYER.Seek", OnMediaSeek ) + +local function OnMediaPause( len ) + + local mpId = net.ReadString() + local mp = MediaPlayer.GetById(mpId) + if not mp then return end + + local state = mp.net.ReadPlayerState() + + if MediaPlayer.DEBUG then + print( "Received MEDIAPLAYER.Pause", mpId, state ) + end + + mp:SetPlayerState( state ) + +end +net.Receive( "MEDIAPLAYER.Pause", OnMediaPause ) diff --git a/lua/autorun/mediaplayer/players/base/cl_net.lua b/lua/autorun/mediaplayer/players/base/cl_net.lua new file mode 100644 index 0000000..2ad096e --- /dev/null +++ b/lua/autorun/mediaplayer/players/base/cl_net.lua @@ -0,0 +1,42 @@ +local net = net + +local EOT = "\4" -- End of transmission + +MEDIAPLAYER.net = {} +local mpnet = MEDIAPLAYER.net + +function mpnet.ReadDuration() + return net.ReadUInt(16) +end + +function mpnet.ReadMedia() + local url = net.ReadString() + + if url == EOT then + return nil + end + + local title = net.ReadString() + local duration = mpnet.ReadDuration() + local ownerName = net.ReadString() + local ownerSteamId = net.ReadString() + + -- Create media object + local media = MediaPlayer.GetMediaForUrl( url ) + + -- Set metadata + media._metadata = { + title = title, + duration = duration + } + + media._OwnerName = ownerName + media._OwnerSteamID = ownerSteamId + + return media +end + +local StateBits = math.CeilPower2(NUM_MP_STATE) / 2 +function mpnet.ReadPlayerState() + return net.ReadUInt(StateBits) +end diff --git a/lua/autorun/mediaplayer/players/base/init.lua b/lua/autorun/mediaplayer/players/base/init.lua new file mode 100644 index 0000000..c7524ab --- /dev/null +++ b/lua/autorun/mediaplayer/players/base/init.lua @@ -0,0 +1,490 @@ +AddCSLuaFile "shared.lua" +AddCSLuaFile "cl_draw.lua" +AddCSLuaFile "cl_fullscreen.lua" +AddCSLuaFile "cl_net.lua" +include "shared.lua" +include "sv_net.lua" + +-- Additional transmit states +TRANSMIT_LOCATION = 4 + +-- Media player network strings +util.AddNetworkString( "MEDIAPLAYER.Update" ) +util.AddNetworkString( "MEDIAPLAYER.Media" ) +util.AddNetworkString( "MEDIAPLAYER.Remove" ) +util.AddNetworkString( "MEDIAPLAYER.Pause" ) +util.AddNetworkString( "MEDIAPLAYER.Seek" ) + + +--[[--------------------------------------------------------- + Listeners +-----------------------------------------------------------]] + +function MEDIAPLAYER:UpdateListeners() + local transmitState = self._TransmitState + local listeners = nil + + if transmitState == TRANSMIT_NEVER then + return + + elseif transmitState == TRANSMIT_ALWAYS then + + listeners = player.GetAll() + + elseif transmitState == TRANSMIT_PVS then + + listeners = player.GetInPVS( self.Entity and self.Entity or self:GetPos() ) + + elseif transmitState == TRANSMIT_LOCATION then + + local loc = self:GetLocation() + + if not Location then + ErrorNoHalt("'Location' module not defined in mediaplayer!\n") + debug.Trace() + return + elseif loc == -1 then + ErrorNoHalt("Invalid location assigned to mediaplayer!\n") + debug.Trace() + return + end + + listeners = Location.GetPlayersInLocation( loc ) + + else + ErrorNoHalt("Invalid transmit state for mediaplayer\n") + debug.Trace() + return + end + + self:SetListeners(listeners) +end + +function MEDIAPLAYER:SetListeners( listeners ) + + local ValidListeners = {} + + -- Filter listeners + for _, ply in pairs(listeners) do + if IsValid(ply) and ply:IsConnected() and not ply:IsBot() then + table.insert( ValidListeners, ply ) + end + end + + -- Find players who should be removed. + -- + -- A = self._Listeners + -- B = listeners + -- (A ∩ B)^c + for _, ply in pairs(self._Listeners) do + if not table.HasValue( ValidListeners, ply ) then + self:RemoveListener( ply ) + end + end + + -- Find players who should be added + for _, ply in pairs(ValidListeners) do + if not self:HasListener(ply) then + self:AddListener( ply ) + end + end + +end + +function MEDIAPLAYER:AddListener( ply ) + + if MediaPlayer.DEBUG then + print( "MEDIAPLAYER.AddListener", self, ply ) + end + + table.insert( self._Listeners, ply ) + + -- Send player queue information + self:BroadcastUpdate(ply) + + -- Send current media to new listener + if (self:GetPlayerState() >= MP_STATE_PLAYING) then + self:SendMedia( self:CurrentMedia(), ply ) + end + + hook.Call( "MediaPlayerAddListener", self, ply ) + +end + +function MEDIAPLAYER:RemoveListener( ply ) + + if MediaPlayer.DEBUG then + print( "MEDIAPLAYER.RemoveListener", self, ply ) + end + + local key = table.RemoveByValue( self._Listeners, ply ) + + if not key then + ErrorNoHalt( "Tried to remove player from media player " .. + "who wasn't listening\n" ) + debug.Trace() + return + end + + -- Inform listener of removal + net.Start( "MEDIAPLAYER.Remove" ) + net.WriteString( self:GetId() ) + net.Send( ply ) + + hook.Call( "MediaPlayerRemoveListener", self, ply ) + +end + +function MEDIAPLAYER:HasListener( ply ) + return table.HasValue( self._Listeners, ply ) +end + + +--[[--------------------------------------------------------- + Queue Management +-----------------------------------------------------------]] + +function MEDIAPLAYER:NextMedia() + + if MediaPlayer.DEBUG then + print( "MEDIAPLAYER.NextMedia" ) + end + + local media = nil + + -- Grab media from the queue if available + if not self:IsQueueEmpty() then + media = table.remove( self._Queue, 1 ) + end + + self:SetMedia( media ) + self:SendMedia( media ) + + self:BroadcastUpdate() + +end + +function MEDIAPLAYER:SendMedia( media, ply ) + + if MediaPlayer.DEBUG then + print( "MEDIAPLAYER.SendMedia", media ) + end + + self:UpdateListeners() + + local startTime = media and media:StartTime() or 0 + + net.Start( "MEDIAPLAYER.Media" ) + net.WriteString( self:GetId() ) + self.net.WriteMedia( media ) + net.WriteInt( startTime, 32 ) + net.Send( ply or self._Listeners ) + +end + + +--[[--------------------------------------------------------- + Media requests +-----------------------------------------------------------]] + +--- +-- Determine whether the player is allowed to request media. Override this for +-- custom behavior. +-- +-- @return boolean +-- +function MEDIAPLAYER:CanPlayerRequestMedia( ply, media ) + -- Check service whitelist if it exists on the mediaplayer + if self.ServiceWhitelist and not table.HasValue(self.ServiceWhitelist, media.Id) then + local names = MediaPlayer.GetValidServiceNames(self.ServiceWhitelist) + + local msg = "The requested media isn't supported; accepted services are as followed:\n" + msg = msg .. table.concat( names, ", " ) + + self:NotifyPlayer( ply, msg ) + + return false + end + + return true +end + +--- +-- Determines if the player has privileges to use media controls (skip, seek, +-- etc.). Override this for custom behavior. +-- +function MEDIAPLAYER:IsPlayerPrivileged( ply ) + return ply == self:GetOwner() or ply:IsAdmin() +end + +--- +-- Determine whether the media should be added to the queue. +-- This should be overwritten if only certain media should be allowed. +-- +-- @return boolean +-- +function MEDIAPLAYER:ShouldAddMedia( media ) + return true +end + +-- TODO: Remove this function in favor of RequestMedia +function MEDIAPLAYER:RequestUrl( url, ply ) + + if not IsValid(ply) then return end + + if MediaPlayer.DEBUG then + print( "MEDIAPLAYER.RequestUrl", url, ply ) + end + + -- Queue must have space for the request + if #self._Queue == self.MaxMediaItems then + self:NotifyPlayer( ply, "The media player queue is full." ) + return + end + + -- Validate the URL + if not MediaPlayer.ValidUrl( url ) then + self:NotifyPlayer( ply, "The requested URL wasn't valid." ) + return + end + + -- Build the media object for the URL + local media = MediaPlayer.GetMediaForUrl( url ) + + self:RequestMedia( media, ply ) + +end + +function MEDIAPLAYER:RequestMedia( media, ply ) + + -- Player must be valid and also a listener + if not ( IsValid(ply) and self:HasListener(ply) and + self:CanPlayerRequestMedia(ply, media) ) then + return + end + + if MediaPlayer.DEBUG then + print( "MEDIAPLAYER.RequestMedia", media, ply ) + end + + -- Queue must have space for the request + if #self._Queue == self.MaxMediaItems then + self:NotifyPlayer( ply, "The media player queue is full." ) + return + end + + -- Make sure the media isn't already in the queue + for _, s in pairs(self._Queue) do + if s.Id == media.Id and s:UniqueID() == media:UniqueID() then + if MediaPlayer.DEBUG then + print("MediaPlayer.RequestMedia: Duplicate request", s.Id, media.Id) + print(media) + print(s) + end + self:NotifyPlayer( ply, "The requested media was already in the queue" ) + return + end + end + + -- TODO: prevent media from playing if this hook returns false(?) + hook.Run( "PreMediaPlayerMediaRequest", self, media, ply ) + + -- self:NotifyPlayer( ply, "Processing media request..." ) + + -- Fetch the media's metadata + media:GetMetadata(function(data, err) + + if not data then + err = err and err or "There was a problem fetching the requested media's metadata." + self:NotifyPlayer( ply, "[Request Error] " .. err ) + return + end + + media:SetOwner( ply ) + + if not self:ShouldAddMedia(media) then + return + end + + -- Add the media to the queue + self:AddMedia( media ) + + local msg = string.format( "Added '%s' to the queue", media:Title() ) + self:NotifyPlayer( ply, msg ) + + self:BroadcastUpdate() + + MediaPlayer.History:LogRequest( media ) + + hook.Run( "PostMediaPlayerMediaRequest", self, media, ply ) + + end) + +end + +function MEDIAPLAYER:RequestPause( ply ) + + -- Player must be valid and also a listener + if not ( IsValid(ply) and self:HasListener(ply) ) then + return + end + + -- Check player priviledges + if not self:IsPlayerPrivileged(ply) then + return + end + + if MediaPlayer.DEBUG then + print( "MEDIAPLAYER.RequestPause", ply ) + end + + -- Toggle player state + if self:GetPlayerState() == MP_STATE_PLAYING then + self:SetPlayerState( MP_STATE_PAUSED ) + else + self:SetPlayerState( MP_STATE_PLAYING ) + end + + net.Start( "MEDIAPLAYER.Pause" ) + net.WriteString( self:GetId() ) + self.net.WritePlayerState( self:GetPlayerState() ) + net.Send( self._Listeners ) + +end + +function MEDIAPLAYER:RequestSkip( ply ) + + if not (self:GetPlayerState() >= MP_STATE_PLAYING) then return end + + -- Player must be valid and also a listener + if not ( IsValid(ply) and self:HasListener(ply) ) then + return + end + + -- Check player priviledges + if not self:IsPlayerPrivileged(ply) then + return + end + + if MediaPlayer.DEBUG then + print( "MEDIAPLAYER.RequestSkip", ply ) + end + + self:NextMedia() + +end + +local function ParseTime( time ) + local tbl = {} + + -- insert fragments in reverse + for fragment, _ in string.gmatch(time, ":?(%d+)") do + table.insert(tbl, 1, tonumber(fragment) or 0) + end + + if #tbl == 0 then + return nil + end + + local seconds = 0 + + for i = 1, #tbl do + seconds = seconds + tbl[i] * math.max(60 ^ (i-1), 1) + end + + return seconds +end + +function MEDIAPLAYER:RequestSeek( ply, seekTime ) + + if not (self:GetPlayerState() >= MP_STATE_PLAYING) then return end + + -- Player must be valid and also a listener + if not ( IsValid(ply) and self:HasListener(ply) ) then + return + end + + -- Check player priviledges + if not self:IsPlayerPrivileged(ply) then + return + end + + if MediaPlayer.DEBUG then + print( "MEDIAPLAYER.RequestSeek", ply, seekTime ) + end + + local media = self:CurrentMedia() + + -- Ignore requests for media that isn't timed + if not media:IsTimed() then + return + end + + -- Convert HH:MM:SS to seconds + local seconds = ParseTime( seekTime ) + if not seconds then return end + + -- Ignore request if time is past the end of the video + if seconds > media:Duration() then + self:NotifyPlayer( ply, "Request seek time was past the end of the media duration" ) + return + end + + -- NOTE: this can result in a negative number if the server was recently + -- started. + local startTime = CurTime() - seconds + + media:StartTime( startTime ) + + self:UpdateListeners() + + net.Start( "MEDIAPLAYER.Seek" ) + net.WriteString( self:GetId() ) + net.WriteInt( startTime, 32 ) + net.Send( self._Listeners ) + +end + + +--[[--------------------------------------------------------- + Media Player Updates +-----------------------------------------------------------]] + +function MEDIAPLAYER:BroadcastUpdate( ply ) + + if MediaPlayer.DEBUG then + print( "MEDIAPLAYER.BroadcastUpdate", ply ) + end + + self:UpdateListeners() + + net.Start( "MEDIAPLAYER.Update" ) + net.WriteString( self:GetId() ) + net.WriteString( self.Name ) + self.net.WritePlayerState( self:GetPlayerState() ) + self:NetWriteUpdate() + net.WriteUInt( #self._Queue, math.CeilPower2(self.MaxMediaItems)/2 ) + for _, media in pairs(self._Queue) do + self.net.WriteMedia(media) + end + net.Send( ply or self._Listeners ) + +end + +function MEDIAPLAYER:NetWriteUpdate() + -- Allows for another media player type to extend update net messages +end + +-- Player requesting queue update +net.Receive( "MEDIAPLAYER.Update", function(len, ply) + local id = net.ReadString() + local mp = MediaPlayer.GetById(id) + if not mp then return end + -- TODO: prevent request spam + mp:BroadcastUpdate(ply) +end ) + + +function MEDIAPLAYER:NotifyPlayer( ply, msg ) + ply:ChatPrint( msg ) +end diff --git a/lua/autorun/mediaplayer/players/base/shared.lua b/lua/autorun/mediaplayer/players/base/shared.lua new file mode 100644 index 0000000..dbf1c38 --- /dev/null +++ b/lua/autorun/mediaplayer/players/base/shared.lua @@ -0,0 +1,367 @@ +local MediaPlayer = MediaPlayer + +--[[--------------------------------------------------------- + Base Media Player +-----------------------------------------------------------]] + +local MEDIAPLAYER = MEDIAPLAYER +MEDIAPLAYER.__index = MEDIAPLAYER + +-- Inherit EventEmitter for all mediaplayer instances +EventEmitter:new(MEDIAPLAYER) + +MEDIAPLAYER.Name = "base" +MEDIAPLAYER.IsMediaPlayer = true +MEDIAPLAYER.MaxMediaItems = 64 +MEDIAPLAYER.NoMedia = "\4" -- end of transmission character + +-- Media Player states +MP_STATE_ENDED = 0 +MP_STATE_PLAYING = 1 +MP_STATE_PAUSED = 2 +NUM_MP_STATE = 3 + +-- +-- Initialize the media player object. +-- +function MEDIAPLAYER:Init(params) + self._Queue = {} -- media queue + self._Media = nil -- current media + self._Owner = nil -- theater owner + + self._State = MP_STATE_ENDED -- waiting for new media + + if SERVER then + + self._TransmitState = TRANSMIT_ALWAYS + self._Listeners = {} + + self._Location = -1 + + else + + self._LastMediaUpdate = 0 + control.Add( KEY_Q, self, self.OnQueueKeyPressed ) + control.Add( KEY_C, self, self.OnQueueKeyPressed ) + + end + + -- Merge in any passed in params + -- table.Merge(self, params or {}) +end + +-- +-- Get whether the media player is valid. +-- +-- @return boolean Whether the media player is valid +-- +function MEDIAPLAYER:IsValid() + return true +end + +-- +-- String coercion metamethod +-- +-- @return String Media player string representation +-- +function MEDIAPLAYER:__tostring() + return string.join( ', ', + self:GetId() ) +end + +-- +-- Get the media player's unique ID. +-- +-- @return Number Media player ID. +-- +function MEDIAPLAYER:GetId() + return self.id +end + +-- +-- Get the media player's type. +-- +-- @return String MP type. +-- +function MEDIAPLAYER:GetType() + return self.Name +end + +function MEDIAPLAYER:GetPlayerState() + return self._State +end + +function MEDIAPLAYER:SetPlayerState( state ) + local current = self._State + self._State = state + + if MediaPlayer.DEBUG then + print( "MEDIAPLAYER.SetPlayerState", state ) + end + + if current ~= state then + self:OnPlayerStateChanged( current, state ) + end +end + +function MEDIAPLAYER:OnPlayerStateChanged( old, new ) + local media = self:GetMedia() + local validMedia = IsValid(media) + + if MediaPlayer.DEBUG then + print( "MEDIAPLAYER.OnPlayerStateChanged", old .. ' => ' .. new ) + end + + if new == MP_STATE_PLAYING then + if validMedia and not media:IsPlaying() then + media:Play() + end + elseif new == MP_STATE_PAUSED then + if validMedia and media:IsPlaying() then + media:Pause() + end + end +end + +-- +-- Get whether the media player is currently playing media. +-- +-- @return boolean Media is playing +-- +function MEDIAPLAYER:IsPlaying() + return self._State == MP_STATE_PLAYING +end + +-- +-- Get the media player's position. +-- +-- @return Vector3 Media player's position +-- +function MEDIAPLAYER:GetPos() + if not self._pos then + self._pos = Vector(0,0,0) + end + return self._pos +end + +-- +-- Get the media player's location ID. +-- +-- @return Number Media player's location ID +-- +function MEDIAPLAYER:GetLocation() + return self._Location +end + +function MEDIAPLAYER:GetOwner() + return self._Owner +end + +function MEDIAPLAYER:SetOwner( ply ) + self._Owner = ply +end + +-- +-- Media player update +-- +function MEDIAPLAYER:Think() + + if SERVER then + self:UpdateListeners() + end + + local media = self:GetMedia() + local validMedia = IsValid(media) + + -- Waiting to play new media + if self._State <= MP_STATE_ENDED then + + -- Check queue for videos to play + -- TODO: perform state change when media is added + if not self:IsQueueEmpty() then + self:OnMediaFinished() + end + + elseif self._State == MP_STATE_PLAYING then + + -- Wait for media to finish + if validMedia and media:IsTimed() then + local time = media:CurrentTime() + local duration = media:Duration() + + if time > duration then + self:OnMediaFinished() + end + end + + end + + if CLIENT and validMedia then + media:Sync() + + -- TODO: check if volume has changed first? + media:Volume( MediaPlayer.Volume() ) + end + +end + +-- +-- Get the currently playing media. +-- +-- @return Media Currently playing media +-- +function MEDIAPLAYER:GetMedia() + return self._Media +end + +MEDIAPLAYER.CurrentMedia = MEDIAPLAYER.GetMedia + +-- +-- Set the currently playing media. +-- +-- @param media Media object. +-- +function MEDIAPLAYER:SetMedia( media ) + self._Media = media + self:OnMediaStarted( media ) + + -- NOTE: media can be nil! + self:emit('mediaChanged', media) +end + +-- +-- Get the media queue. +-- TODO: Remove this as it should only be accessed internally? +-- +-- @return table Media queue. +-- +function MEDIAPLAYER:GetMediaQueue() + return self._Queue +end + +-- +-- Clear the media queue. +-- +function MEDIAPLAYER:ClearMediaQueue() + self._Queue = {} + if SERVER then + self:BroadcastUpdate() + end +end + +-- +-- Get whether the media queue is empty. +-- +-- @return boolean Whether the queue is empty +-- +function MEDIAPLAYER:IsQueueEmpty() + return #self._Queue == 0 +end + +-- +-- Add media to the queue. +-- +-- @param media Media object. +-- +function MEDIAPLAYER:AddMedia( media ) + if not media then return end + + if SERVER then + -- Add an extra second for client buffering time + media:Duration( media:Duration() + 1 ) + end + + table.insert( self._Queue, media ) +end + +-- +-- Event called when media should begin playing. +-- +-- @param media Media object to be played. +-- +function MEDIAPLAYER:OnMediaStarted( media ) + + media = media or self:CurrentMedia() + + if MediaPlayer.DEBUG then + print( "MEDIAPLAYER.OnMediaStarted", media ) + end + + if IsValid(media) then + + if SERVER then + media:StartTime( CurTime() + 1 ) + else + self._LastMediaUpdate = RealTime() + end + + if SERVER then + self:SetPlayerState( MP_STATE_PLAYING ) + end + + self:emit('mediaStarted', media) + + elseif SERVER then + self:SetPlayerState( MP_STATE_ENDED ) + end + +end + +-- +-- Event called when media should stop playing and the next in the queue +-- should begin. +-- +-- @param media Media object to stop. +-- +function MEDIAPLAYER:OnMediaFinished( media ) + + media = media or self:CurrentMedia() + + if MediaPlayer.DEBUG then + print( "MEDIAPLAYER.OnMediaFinished", media ) + end + + if SERVER then + self:SetPlayerState( MP_STATE_ENDED ) + end + + self._Media = nil + + if CLIENT and IsValid(media) then + -- TODO: Reuse browser if it was the same video type + media:Stop() + end + + self:emit('mediaFinished', media) + + if SERVER then + self:NextMedia() + end + +end + +-- +-- Event called when the media player is to be removed/destroyed. +-- +function MEDIAPLAYER:Remove() + MediaPlayer.Destroy( self ) + + if SERVER then + + -- Remove all listeners + for _, ply in pairs( self._Listeners ) do + -- TODO: it's probably better not to send individual net messages + -- for each player removed. + self:RemoveListener( ply ) + end + + else + + local media = self:CurrentMedia() + + if IsValid(media) then + media:Stop() + end + + end +end diff --git a/lua/autorun/mediaplayer/players/base/sv_net.lua b/lua/autorun/mediaplayer/players/base/sv_net.lua new file mode 100644 index 0000000..d9566dc --- /dev/null +++ b/lua/autorun/mediaplayer/players/base/sv_net.lua @@ -0,0 +1,27 @@ +local net = net + +local EOT = "\4" -- End of transmission + +MEDIAPLAYER.net = {} +local mpnet = MEDIAPLAYER.net + +function mpnet.WriteDuration( seconds ) + net.WriteUInt( seconds, 16 ) +end + +function mpnet.WriteMedia( media ) + if media then + net.WriteString( media:Url() ) + net.WriteString( media:Title() ) + mpnet.WriteDuration( media:Duration() ) + net.WriteString( media:OwnerName() ) + net.WriteString( media:OwnerSteamID() ) + else + net.WriteString( EOT ) + end +end + +local StateBits = math.CeilPower2(NUM_MP_STATE) / 2 +function mpnet.WritePlayerState( state ) + net.WriteUInt(state, StateBits) +end diff --git a/lua/autorun/mediaplayer/players/entity/cl_init.lua b/lua/autorun/mediaplayer/players/entity/cl_init.lua new file mode 100644 index 0000000..c057870 --- /dev/null +++ b/lua/autorun/mediaplayer/players/entity/cl_init.lua @@ -0,0 +1,106 @@ +include "shared.lua" + +DEFINE_BASECLASS( "mp_base" ) + +local pcall = pcall +local print = print +local Angle = Angle +local IsValid = IsValid +local ValidPanel = ValidPanel +local Vector = Vector +local cam = cam +local Start3D = cam.Start3D +local Start3D2D = cam.Start3D2D +local End3D2D = cam.End3D2D +local draw = draw +local math = math +local string = string +local surface = surface + +local FullscreenCvar = MediaPlayer.Cvars.Fullscreen + +MEDIAPLAYER.Enable3DAudio = true + +function MEDIAPLAYER:NetReadUpdate() + local entIndex = net.ReadUInt(16) + local ent = Entity(entIndex) + local mpEnt = self.Entity + + if MediaPlayer.DEBUG then + print("MEDIAPLAYER.NetReadUpdate[entity]: ", ent, entIndex) + end + + if ent ~= mpEnt then + if IsValid(ent) and ent ~= NULL then + ent:InstallMediaPlayer( self ) + else + -- Wait until the entity becomes valid + self._EntIndex = entIndex + end + end +end + +local RenderScale = 0.1 +local InfoScale = 1/17 + +function MEDIAPLAYER:Draw( bDrawingDepth, bDrawingSkybox ) + + local ent = self.Entity + + if --bDrawingSkybox or + FullscreenCvar:GetBool() or -- Don't draw if we're drawing fullscreen + not IsValid(ent) or + (ent.IsDormant and ent:IsDormant()) then + return + end + + local media = self:CurrentMedia() + + -- TODO: Draw thumbnail at far distances? + + local w, h, pos, ang = ent:GetMediaPlayerPosition() + + -- Render scale + local rw, rh = w / RenderScale, h / RenderScale + + if IsValid(media) then + + -- Custom media draw function + if media.Draw then + Start3D2D( pos, ang, RenderScale ) + media:Draw( rw, rh ) + End3D2D() + end + -- TODO: else draw 'not yet implemented' screen? + + -- Media info + Start3D2D( pos, ang, InfoScale ) + local iw, ih = w / InfoScale, h / InfoScale + local succ, err = pcall( self.DrawMediaInfo, self, media, iw, ih ) + if not succ then + print( err ) + end + End3D2D() + + else + + local browser = MediaPlayer.GetIdlescreen() + + if ValidPanel(browser) then + Start3D2D( pos, ang, RenderScale ) + self:DrawHTML( browser, rw, rh ) + End3D2D() + end + + end + +end + +function MEDIAPLAYER:SetMedia( media ) + if media and self.Enable3DAudio then + -- Set entity on media for 3D support + media.Entity = self:GetEntity() + end + + BaseClass.SetMedia( self, media ) +end diff --git a/lua/autorun/mediaplayer/players/entity/init.lua b/lua/autorun/mediaplayer/players/entity/init.lua new file mode 100644 index 0000000..a8905ef --- /dev/null +++ b/lua/autorun/mediaplayer/players/entity/init.lua @@ -0,0 +1,10 @@ +AddCSLuaFile "shared.lua" +AddCSLuaFile "sh_meta.lua" +include "shared.lua" + +function MEDIAPLAYER:NetWriteUpdate() + -- Write the entity index since the actual entity may not yet exist on a + -- client that's not fully connected. + local entIndex = IsValid(self.Entity) and self.Entity:EntIndex() or 0 + net.WriteUInt(entIndex, 16) +end diff --git a/lua/autorun/mediaplayer/players/entity/sh_meta.lua b/lua/autorun/mediaplayer/players/entity/sh_meta.lua new file mode 100644 index 0000000..b471b98 --- /dev/null +++ b/lua/autorun/mediaplayer/players/entity/sh_meta.lua @@ -0,0 +1,85 @@ +--[[--------------------------------------------------------- + Media Player Entity Meta +-----------------------------------------------------------]] + +local EntityMeta = FindMetaTable("Entity") +if not EntityMeta then return end + +function EntityMeta:GetMediaPlayer() + return self._mp +end + +-- +-- Installs a media player reference to the entity. +-- +-- @param Table|String mp Media player table or string type. +function EntityMeta:InstallMediaPlayer( mp ) + if not istable(mp) then + local mpType = isstring(mp) and mp or "entity" + + if not MediaPlayer.IsValidType(mpType) then + ErrorNoHalt("ERROR: Attempted to install invalid mediaplayer type onto an entity!\n") + ErrorNoHalt("ENTITY: " .. tostring(self) .. "\n") + ErrorNoHalt("TYPE: " .. tostring(mpType) .. "\n") + mpType = "entity" -- default + end + + local mpId = "Entity" .. self:EntIndex() + mp = MediaPlayer.Create( mpId, mpType ) + end + + self._mp = mp + self._mp:SetEntity(self) + + if isfunction(self.SetupMediaPlayer) then + self:SetupMediaPlayer(mp) + end +end + +local DefaultConfig = { + offset = Vector(0,0,0), -- translation from entity origin + angle = Angle(0,0,0), -- rotation + -- attachment = "corner" -- attachment name + width = 64, -- screen width + height = 64 * 9/16 -- screen height +} + +function EntityMeta:GetMediaPlayerPosition() + local cfg = self.PlayerConfig or DefaultConfig + + local w = (cfg.width or DefaultConfig.width) + local h = (cfg.height or DefaultConfig.height) + + local pos, ang + + if cfg.attachment then + local idx = self:LookupAttachment(cfg.attachment) + if not idx then + local err = string.format("MediaPlayer:Entity.Draw: Invalid attachment '%s'\n", cfg.attachment) + Error(err) + end + + -- Get attachment orientation + local attach = self:GetAttachment(idx) + pos = attach.pos + ang = attach.ang + else + pos = self:GetPos() -- TODO: use GetRenderOrigin? + end + + -- Apply offset + if cfg.offset then + pos = pos + + self:GetForward() * cfg.offset.x + + self:GetRight() * cfg.offset.y + + self:GetUp() * cfg.offset.z + end + + -- Set angles + ang = ang or self:GetAngles() -- TODO: use GetRenderAngles? + ang:RotateAroundAxis( ang:Right(), cfg.angle.p ) + ang:RotateAroundAxis( ang:Up(), cfg.angle.y ) + ang:RotateAroundAxis( ang:Forward(), cfg.angle.r ) + + return w, h, pos, ang +end diff --git a/lua/autorun/mediaplayer/players/entity/shared.lua b/lua/autorun/mediaplayer/players/entity/shared.lua new file mode 100644 index 0000000..16490b8 --- /dev/null +++ b/lua/autorun/mediaplayer/players/entity/shared.lua @@ -0,0 +1,89 @@ +include "sh_meta.lua" + +DEFINE_BASECLASS( "mp_base" ) + +--[[--------------------------------------------------------- + Entity Media Player +-----------------------------------------------------------]] + +local MEDIAPLAYER = MEDIAPLAYER +MEDIAPLAYER.Name = "entity" + +function MEDIAPLAYER:IsValid() + return self.Entity and IsValid(self.Entity) +end + +function MEDIAPLAYER:Init(...) + BaseClass.Init(self, ...) + + if SERVER then + -- Manually manage listeners by default + self._TransmitState = TRANSMIT_NEVER + else--if CLIENT and self.Render then + hook.Add( "HUDPaint", self, self.DrawFullscreen ) + hook.Add( "PostDrawOpaqueRenderables", self, self.Draw ) + end +end + +function MEDIAPLAYER:SetEntity(ent) + self.Entity = ent +end + +function MEDIAPLAYER:GetEntity() + -- Clients may wait for the entity to become valid + if CLIENT and self._EntIndex then + local ent = Entity(self._EntIndex) + + if IsValid(ent) and ent ~= NULL then + ent:InstallMediaPlayer(self) + self._EntIndex = nil + else + return nil + end + end + + return self.Entity +end + +function MEDIAPLAYER:GetPos() + return IsValid(self.Entity) and self.Entity:GetPos() or Vector(0,0,0) +end + +function MEDIAPLAYER:GetLocation() + if IsValid(self.Entity) and self.Entity.Location then + return self.Entity:Location() + end + return self._Location +end + +function MEDIAPLAYER:Think() + BaseClass.Think(self) + + local ent = self:GetEntity() + + if IsValid(ent) then + -- Lua refresh fix + if ent._mp ~= self then + self:Remove() + end + elseif SERVER then + -- Only remove on the server since the client may still be connecting + -- and the entity will be created when they finish. + self:Remove() + end +end + +function MEDIAPLAYER:Remove() + -- remove draw hooks + if CLIENT then + hook.Remove( "HUDPaint", self ) + hook.Remove( "PostDrawOpaqueRenderables", self ) + end + + -- remove reference to media player installed on entity + if self.Entity then + self.Entity._mp = nil + end + + BaseClass.Remove(self) +end diff --git a/lua/autorun/mediaplayer/services/audiofile/cl_init.lua b/lua/autorun/mediaplayer/services/audiofile/cl_init.lua new file mode 100644 index 0000000..ba16309 --- /dev/null +++ b/lua/autorun/mediaplayer/services/audiofile/cl_init.lua @@ -0,0 +1,297 @@ +include "shared.lua" + +DEFINE_BASECLASS( "mp_service_base" ) + +-- http://www.un4seen.com/doc/#bass/BASS_StreamCreateURL.html + +SERVICE.StreamOptions = { "noplay", "noblock" } + +function SERVICE:Volume( volume ) + + volume = BaseClass.Volume( self, volume ) + + if IsValid(self.Channel) then + local vol = volume > 1 and volume/100 or volume + + -- IGModAudioChannel is limited by the actual gmod volume + local gmvolume = GetConVarNumber("volume") + if gmvolume > vol then + vol = vol / gmvolume + else + vol = 1 + end + + self.Channel:SetVolume( vol ) + end + + return volume + +end + +function SERVICE:Play() + + BaseClass.Play( self ) + + if IsValid(self.Channel) then + self.Channel:Play() + else + local settings = table.Copy(self.StreamOptions) + + -- .ogg files can't seem to use 3d? + if IsValid(self.Entity) and not self.url:match(".ogg") then + table.insert(settings, "3d") + end + + settings = table.concat(settings, " ") + + sound.PlayURL( self.url, settings, function( channel ) + if IsValid(channel) then + self.Channel = channel + + -- The song may have been skipped before the channel was + -- created, only play if the media state is set to play. + if self:IsPlaying() then + self:Volume() + self:Sync() + + self.Channel:Play() + end + + self:emit('channelReady', channel) + else + ErrorNoHalt( "There was a problem playing the audio file: " .. self.url .. "\n" ) + end + end ) + end + +end + +function SERVICE:Pause() + BaseClass.Pause(self) + + if IsValid(self.Channel) then + self.Channel:Pause() + end +end + +function SERVICE:Stop() + BaseClass.Stop(self) + + if IsValid(self.Channel) then + self.Channel:Stop() + end +end + +function SERVICE:Sync() + if self:IsPlaying() and IsValid(self.Channel) then + if self:IsTimed() then + self:SyncTime() + end + + self:SyncEntityPos() + end +end + +function SERVICE:SyncTime() + local state = self.Channel:GetState() + + if state ~= GMOD_CHANNEL_STALLED then + local duration = self.Channel:GetLength() + local seekTime = math.min(duration, self:CurrentTime()) + local curTime = self.Channel:GetTime() + local diffTime = math.abs(curTime - seekTime) + + if diffTime > 5 then + self.Channel:SetTime( seekTime ) + end + end +end + +function SERVICE:SyncEntityPos() + if IsValid(self.Entity) then + + if self.Channel:Is3D() then + -- apparently these are the default values? + self.Channel:Set3DFadeDistance( 500, 1000 ) + + self.Channel:SetPos( self.Entity:GetPos() ) + else + -- TODO: Fake 3D volume + -- http://facepunch.com/showthread.php?t=1302124&p=41975238&viewfull=1#post41975238 + + -- local volume = BaseClass.Volume( self, volume ) + -- local vol = volume > 1 and volume/100 or volume + -- self.Channel:SetVolume( vol ) + end + + end +end + +function SERVICE:PreRequest( callback ) + + -- LocalPlayer():ChatPrint( "Prefetching data for '" .. self.url .. "'..." ) + + sound.PlayURL( self.url, "noplay", function( channel ) + + if MediaPlayer.DEBUG then + print("AUDIOFILE.PreRequest", channel) + end + + if IsValid(channel) then + -- Set metadata to later send to the server; IGModAudioChannel is + -- only accessible on the client. + self._metadata = {} + self._metadata.title = channel:GetFileName() + self._metadata.duration = channel:GetLength() + + -- TODO: limit the duration in some way so a client doesn't try to + -- spoof this + + callback() + + channel:Stop() + else + callback("There was a problem prefetching audio metadata.") + end + + end ) + +end + +function SERVICE:NetWriteRequest() + net.WriteString( self:Title() ) + net.WriteUInt( self:Duration(), 16 ) +end + +--[[--------------------------------------------------------- + Draw 3D2D +-----------------------------------------------------------]] + +local IsValid = IsValid +local draw = draw +local math = math +local surface = surface + +local VisualizerBgColor = Color(44, 62, 80, 255) +local VisualizerBarColor = Color(52, 152, 219) + +local BandGridHeight = 16 +local BandGridWidth = math.ceil( BandGridHeight * 16/9 ) + +local NumBands = 256 +local BandStepSize = math.floor(NumBands / BandGridWidth) + +local BarPadding = 1 + +-- local BandGrid = {} + +-- for x = 1, BandGridWidth do +-- BandGrid[x] = {} +-- for y = 1, BandGridHeight do +-- BandGrid[x][y] = +-- end +-- end + +-- http://inchoatethoughts.com/a-wpf-spectrum-analyzer-for-audio-visualization +-- http://wpfsvl.codeplex.com/SourceControl/latest#WPFSoundVisualizationLib/Main/Source/WPFSoundVisualizationLibrary/Spectrum Analyzer/SpectrumAnalyzer.cs + +--[[function MEDIAPLAYER:DrawSpectrumAnalyzer( media, w, h ) + + local channel = media.Channel + + if channel:GetState() ~= GMOD_CHANNEL_PLAYING then + return + end + + local fft = {} + channel:FFT( fft, FFT_512 ) + + surface.SetDrawColor(VisualizerBarColor) + + local BarWidth = math.floor(w / BandGridWidth) + local b0 = 1 + + for x = 0, BandGridWidth do + local sum = 0 + local sc = 0 + local b1 = math.pow(2, x * 10.0 / BandGridWidth) + + if (b1 > NumBands) then b1 = NumBands end + if (b1<=b0) then b1 = b0 end + + sc=10+b1-b0 + + while b0 < b1 do + sum = sum + fft[b0] + b0 = b0 + 1 + end + + local BarHeight = math.floor(math.sqrt(sum/math.log10(sc)) * 1.7 * h) + BarHeight = math.Clamp(BarHeight, 0, h) + + surface.DrawRect( + (x * BarWidth) + BarPadding, + h - BarHeight, + BarWidth - (BarPadding * 2), + BarHeight + ) + end + +end]] + +local BANDS = 28 + +function DrawSpectrumAnalyzer( channel, w, h ) + + -- Background + surface.SetDrawColor( VisualizerBgColor ) + surface.DrawRect( 0, 0, w, h ) + + if channel:GetState() ~= GMOD_CHANNEL_PLAYING then + return + end + + local fft = {} + channel:FFT( fft, FFT_2048 ) + local b0 = 1 + + -- surface.SetDrawColor(VisualizerBarColor) + + local x, y + + for x = 0, BANDS do + local sum = 0 + local sc = 0 + local b1 = math.pow(2,x*10.0/(BANDS-1)) + + if (b1>1023) then b1=1023 end + if (b1<=b0) then b1=b0+1 end + sc=10+b1-b0; + while b0 < b1 do + sum = sum + fft[b0] + b0 = b0 + 1 + end + + y = (math.sqrt(sum/math.log10(sc))*1.7*h)-4 + y = math.Clamp(y, 0, h) + + local col = HSVToColor( 120 - (120 * y/h), 1, 1 ) + surface.SetDrawColor(col) + + surface.DrawRect( + math.ceil(x*(w/BANDS)), + math.ceil(h - y - 1), + math.ceil(w/BANDS) - 2, + y + 1 + ) + end +end + + +function SERVICE:Draw( w, h ) + + if IsValid(self.Channel) then + DrawSpectrumAnalyzer( self.Channel, w, h ) + end + +end diff --git a/lua/autorun/mediaplayer/services/audiofile/init.lua b/lua/autorun/mediaplayer/services/audiofile/init.lua new file mode 100644 index 0000000..a5249f3 --- /dev/null +++ b/lua/autorun/mediaplayer/services/audiofile/init.lua @@ -0,0 +1,112 @@ +AddCSLuaFile "shared.lua" +include "shared.lua" + +local urllib = url +local FilenamePattern = "([^/]+)%.%w-$" + +local function titleFallback(self, callback) + local path = self.urlinfo.path + path = string.match( path, FilenamePattern ) -- get filename + + title = urllib.unescape( path ) + self._metadata.title = title + + self:SetMetadata(self._metadata, true) + MediaPlayer.Metadata:Save(self) + + callback(self._metadata) +end + +--[[local function id3(self, callback) + self:Fetch( self.url, + function(body, len, headers, code) + local title, artist + + -- check header + if body:sub(1, 4) == "TAG+" then + title = body:sub(5, 56) + artist = body:sub(57, 116) + elseif body:sub(1, 3) == "TAG" then + title = body:sub(4, 33) + artist = body:sub(34, 63) + else + titleFallback(self, callback) + return + end + + title = title:Trim() + artist = artist:Trim() + + print("ID3 SUCCESS:", title, artist) + + if artist:len() > 0 then + title = artist .. ' - ' .. title + end + + self._metadata.title = title + + callback(self._metadata) + end, + + function() + titleFallback(self, callback) + end, + + { + ["Range"] = "bytes=-128" + } + ) +end]] + +function SERVICE:GetMetadata( callback ) + + local ext = self:GetExtension() + + -- if ext == 'mp3' then + -- id3(self, callback) + -- else + + if not self._metadata then + self._metadata = { + title = "Unknown audio", + duration = 0 + } + end + + if callback then + self:SetMetadata(self._metadata, true) + MediaPlayer.Metadata:Save(self) + + callback(self._metadata) + end + + -- end + +end + +function SERVICE:GetExtension() + if not self._extension then + self._extension = string.GetExtensionFromFilename(self.url) + end + return self._extension +end + +function SERVICE:NetReadRequest() + + if not self.PrefetchMetadata then return end + + local title = net.ReadString() + + -- If the title is just the URL, grab just the filename instead + if title == self.url then + local path = self.urlinfo.path + path = string.match( path, FilenamePattern ) -- get filename + + title = urllib.unescape( path ) + end + + self._metadata = self._metadata or {} + self._metadata.title = title + self._metadata.duration = net.ReadUInt( 16 ) + +end diff --git a/lua/autorun/mediaplayer/services/audiofile/shared.lua b/lua/autorun/mediaplayer/services/audiofile/shared.lua new file mode 100644 index 0000000..410f573 --- /dev/null +++ b/lua/autorun/mediaplayer/services/audiofile/shared.lua @@ -0,0 +1,22 @@ +local urllib = url + +SERVICE.Name = "Audio file" +SERVICE.Id = "af" + +SERVICE.PrefetchMetadata = true + +local SupportedEncodings = { + '([^/]+%.[mM][pP]3)$', -- mp3 + '([^/]+%.[wW][aA][vV])$', -- wav + '([^/]+%.[oO][gG][gG])$' -- ogg +} + +function SERVICE:Match( url ) + -- check supported encodings + for _, encoding in pairs(SupportedEncodings) do + if url:find(encoding) then + return true + end + end + return false +end diff --git a/lua/autorun/mediaplayer/services/base/cl_init.lua b/lua/autorun/mediaplayer/services/base/cl_init.lua new file mode 100644 index 0000000..73117e0 --- /dev/null +++ b/lua/autorun/mediaplayer/services/base/cl_init.lua @@ -0,0 +1,33 @@ +include "shared.lua" + +function SERVICE:Volume( volume ) + if volume then + self._volume = tonumber(volume) or self._volume + end + return self._volume +end + +function SERVICE:IsPaused() + return self._PauseTime ~= nil +end + +function SERVICE:Stop() + self._playing = false + self:emit('stop') +end + +function SERVICE:PlayPause() + if self:IsPlaying() then + self:Pause() + else + self:Play() + end +end + +function SERVICE:Sync() + -- Implement this in timed services +end + +function SERVICE:NetWriteRequest() + -- Send any additional net data here +end diff --git a/lua/autorun/mediaplayer/services/base/init.lua b/lua/autorun/mediaplayer/services/base/init.lua new file mode 100644 index 0000000..6d52cee --- /dev/null +++ b/lua/autorun/mediaplayer/services/base/init.lua @@ -0,0 +1,102 @@ +AddCSLuaFile "shared.lua" +include "shared.lua" + +local MaxTitleLength = 128 + +function SERVICE:SetOwner( ply ) + self._Owner = ply + self._OwnerName = ply:Nick() + self._OwnerSteamID = ply:SteamID() +end + +function SERVICE:SetMetadata( metadata, new ) + self._metadata = metadata + + if new then + local title = self._metadata.title or "Unknown" + title = title:sub(1, MaxTitleLength) + + -- Escape any '%' char with a letter following it + title = title:gsub('%%%a', '%%%%') + + self._metadata.title = title + end +end + +function SERVICE:GetMetadata( callback ) + + if not self._metadata then + self._metadata = { + title = "Base service", + duration = -1, + url = "", + thumbnail = "" + } + end + + callback(self._metadata) + +end + +local HttpHeaders = { + ["Cache-Control"] = "no-cache", + ["Connection"] = "keep-alive", + + -- Required for Google API requests; uses browser API key. + ["Referer"] = MediaPlayer.GetConfigValue('google.referrer'), + + -- Don't use improperly formatted GMod user agent in case anything actually + -- checks the user agent. + ["User-Agent"] = "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/35.0.1916.114 Safari/537.36" +} + +function SERVICE:Fetch( url, onReceive, onFailure, headers ) + + if MediaPlayer.DEBUG then + print( "SERVICE.Fetch", url ) + end + + local request = { + url = url, + method = "GET", + + success = function( code, body, headers ) + if MediaPlayer.DEBUG then + print("HTTP Results["..code.."]:", url) + PrintTable(headers) + print(body) + end + + if isfunction(onReceive) then + onReceive( body, body:len(), headers, code ) + end + end, + + failed = function( err ) + if isfunction(onFailure) then + onFailure( err ) + end + end + } + + -- Pass in extra headers + if headers then + local tbl = table.Copy( HttpHeaders ) + table.Merge( tbl, headers ) + request.headers = tbl + else + request.headers = HttpHeaders + end + + if MediaPlayer.DEBUG then + print "MediaPlayer.Service.Fetch REQUESTING" + PrintTable(request) + end + + HTTP(request) + +end + +function SERVICE:NetReadRequest() + -- Read any additional net data here +end diff --git a/lua/autorun/mediaplayer/services/base/shared.lua b/lua/autorun/mediaplayer/services/base/shared.lua new file mode 100644 index 0000000..26673cf --- /dev/null +++ b/lua/autorun/mediaplayer/services/base/shared.lua @@ -0,0 +1,191 @@ +local string = string +local urllib = url +local CurTime = CurTime + +SERVICE.Name = "Base Service" +SERVICE.Id = "base" +SERVICE.Abstract = true + +-- Inherit EventEmitter for all service instances +EventEmitter:new(SERVICE) + +local OwnerInfoPattern = "%s [%s]" + +function SERVICE:New( url ) + local obj = setmetatable( {}, { + __index = self, + __tostring = self.__tostring + } ) + + obj.url = url + + local success, urlinfo = pcall(urllib.parse2, url) + obj.urlinfo = success and urlinfo or {} + + if CLIENT then + obj._playing = false + obj._volume = 0.33 + end + + return obj +end + +function SERVICE:__tostring() + return string.join( ', ', + self:Title(), + string.FormatSeconds(self:Duration()), + self:OwnerName() ) +end + +-- +-- Determines if the media is valid. +-- +-- @return boolean +-- +function SERVICE:IsValid() + return true +end + +-- +-- Determines if the media supports the given URL. +-- +-- @param url URL. +-- @return boolean +-- +function SERVICE:Match( url ) + return false +end + +-- +-- Gives the unique data used as part of the primary key in the metadata +-- database. +-- +-- @return String +-- +function SERVICE:Data() + return self._data +end + +function SERVICE:Owner() + return self._Owner +end + +SERVICE.GetOwner = SERVICE.Owner + +function SERVICE:OwnerName() + return self._OwnerName +end + +function SERVICE:OwnerSteamID() + return self._OwnerSteamID +end + +function SERVICE:OwnerInfo() + return OwnerInfoPattern:format( self._OwnerName, self._OwnerSteamID ) +end + +function SERVICE:Title() + return self._metadata and self._metadata.title or "Unknown" +end + +function SERVICE:Duration( duration ) + if duration then + self._metadata = self._metadata or {} + self._metadata.duration = duration + end + + return self._metadata and self._metadata.duration or -1 +end + +-- +-- Determines whether the media is timed. +-- +-- @return boolean +-- +function SERVICE:IsTimed() + return true +end + +function SERVICE:Thumbnail() + return self._metadata and self._metadata.thumbnail +end + +function SERVICE:Url() + return self.url +end + +SERVICE.URL = SERVICE.Url + +function SERVICE:UniqueID() + if not self._id then + local data = self:Data() + if not data then + data = util.CRC(self.url) + end + + -- e.g. yt-G2MORmw703o + self._id = string.format( "%s-%s", self.Id, data ) + end + + return self._id +end + +--[[---------------------------------------------------------------------------- + Playback +------------------------------------------------------------------------------]] + +function SERVICE:StartTime( seconds ) + if type(seconds) == 'number' then + if self._PauseTime then + self._PauseTime = CurTime() + end + + self._StartTime = seconds + end + + if self._PauseTime then + local diff = self._PauseTime - self._StartTime + return CurTime() - diff + else + return self._StartTime + end +end + +function SERVICE:CurrentTime() + if self._StartTime then + if self._PauseTime then + return self._PauseTime - self._StartTime + else + return CurTime() - self._StartTime + end + else + return -1 + end +end + +function SERVICE:IsPlaying() + return self._playing +end + +function SERVICE:Play() + if self._PauseTime then + -- Update start time to match the time when paused + self._StartTime = CurTime() - (self._PauseTime - self._StartTime) + self._PauseTime = nil + end + + self._playing = true + + if CLIENT then + self:emit('play') + end +end + +function SERVICE:Pause() + self._PauseTime = CurTime() + self._playing = false + + if CLIENT then + self:emit('pause') + end +end diff --git a/lua/autorun/mediaplayer/services/browser.lua b/lua/autorun/mediaplayer/services/browser.lua new file mode 100644 index 0000000..31aa146 --- /dev/null +++ b/lua/autorun/mediaplayer/services/browser.lua @@ -0,0 +1,130 @@ +DEFINE_BASECLASS( "mp_service_base" ) + +SERVICE.Name = "Browser Base" +SERVICE.Id = "browser" +SERVICE.Abstract = true + +if CLIENT then + + function SERVICE:GetBrowser() + return self.Browser + end + + function SERVICE:OnBrowserReady( browser ) + -- TODO: make sure this resolution is correct + local resolution = MediaPlayer.Resolution() + MediaPlayer.SetBrowserSize( browser, resolution * 16/9, resolution ) + + -- Implement this in a child service + end + + function SERVICE:SetVolume( volume ) + -- Implement this in a child service + end + + function SERVICE:Volume( volume ) + local origVolume = volume + + volume = BaseClass.Volume( self, volume ) + + if origVolume and ValidPanel( self.Browser ) then + self:SetVolume( volume ) + end + + return volume + end + + function SERVICE:Play() + + BaseClass.Play( self ) + + if self.Browser and ValidPanel(self.Browser) then + self:OnBrowserReady( self.Browser ) + else + + self._promise = browserpool.get(function( panel ) + + if not panel then + return + end + + if self._promise then + self._promise = nil + end + + self.Browser = panel + self:OnBrowserReady( panel ) + + end) + end + + end + + function SERVICE:Stop() + BaseClass.Stop( self ) + + if self._promise then + self._promise:Cancel('Service has been stopped') + end + + if self.Browser then + browserpool.release( self.Browser ) + self.Browser = nil + end + end + + local StartHtml = [[ + + + + + Media Player + + + + ]] + + local EndHtml = [[ + + + ]] + + function SERVICE.WrapHTML( html ) + return table.concat({ StartHtml, html, EndHtml }) + end + + --[[--------------------------------------------------------- + Draw 3D2D + -----------------------------------------------------------]] + + local ValidPanel = ValidPanel + local SetDrawColor = surface.SetDrawColor + local DrawRect = surface.DrawRect + local HTMLTexture = draw.HTMLTexture + + function SERVICE:Draw( w, h ) + + if ValidPanel(self.Browser) then + SetDrawColor( 0, 0, 0, 255 ) + DrawRect( 0, 0, w, h ) + HTMLTexture( self.Browser, w, h ) + end + + end + +end diff --git a/lua/autorun/mediaplayer/services/googledrive/cl_init.lua b/lua/autorun/mediaplayer/services/googledrive/cl_init.lua new file mode 100644 index 0000000..4e3e8b1 --- /dev/null +++ b/lua/autorun/mediaplayer/services/googledrive/cl_init.lua @@ -0,0 +1,31 @@ +include "shared.lua" + +DEFINE_BASECLASS( "mp_service_browser" ) + +-- data:text/html, + +-- https://docs.google.com/file/d/0B1K_ByAqaFKGamdrajd6WXFUSEs0VHI4eTJHNHpPdw/preview + +local EmbedHtml = [[ +]] + +SERVICE.VideoUrlFormat = "https://video.google.com/get_player?docid=%s&enablejsapi=1&autoplay=1&controls=0&modestbranding=1&rel=0&showinfo=0&wmode=opaque&ps=docs&partnerid=30" + +function SERVICE:OnBrowserReady( browser ) + + BaseClass.OnBrowserReady( self, browser ) + + local fileId = self:GetGoogleDriveFileId() + + local url = self.VideoUrlFormat:format(fileId) + local curTime = self:CurrentTime() + + -- Add start time to URL if the video didn't just begin + if self:IsTimed() and curTime > 3 then + url = url .. "&start=" .. math.Round(curTime) + end + + local html = self.WrapHTML( EmbedHtml:format(url) ) + browser:SetHTML( html ) + +end diff --git a/lua/autorun/mediaplayer/services/googledrive/init.lua b/lua/autorun/mediaplayer/services/googledrive/init.lua new file mode 100644 index 0000000..1b58453 --- /dev/null +++ b/lua/autorun/mediaplayer/services/googledrive/init.lua @@ -0,0 +1,87 @@ +AddCSLuaFile "shared.lua" +include "shared.lua" + +-- TODO: +-- https://video.google.com/get_player?wmode=opaque&ps=docs&partnerid=30&docid=0B9Kudw3An4Hnci1VZ0pwcHhJc00&enablejsapi=1 +-- http://stackoverflow.com/questions/17779197/google-drive-embed-no-iframe +-- https://developers.google.com/drive/v2/reference/files/get + +local APIKey = MediaPlayer.GetConfigValue('google.api_key') +local MetadataUrl = "https://www.googleapis.com/drive/v2/files/%s?key=%s" + +local SupportedExtensions = { 'mp4' } + +local function OnReceiveMetadata( self, callback, body ) + + local metadata = {} + + local resp = util.JSONToTable( body ) + if not resp then + return callback(false) + end + + if resp.error then + return callback(false, table.Lookup(resp, 'error.message')) + end + + local ext = resp.fileExtension or '' + + if not table.HasValue(SupportedExtensions, ext) then + return callback(false, 'MediaPlayer currently only supports .mp4 Google Drive videos') + end + + metadata.title = resp.title + metadata.thumbnail = resp.thumbnailLink + + -- TODO: duration? etc. + -- no duration metadata returned :( + metadata.duration = 3600 * 4 -- default to 4 hours + + self:SetMetadata(metadata, true) + MediaPlayer.Metadata:Save(self) + + callback(self._metadata) + +end + +function SERVICE:GetMetadata( callback ) + if self._metadata then + callback( self._metadata ) + return + end + + local cache = MediaPlayer.Metadata:Query(self) + + if MediaPlayer.DEBUG then + print("MediaPlayer.GetMetadata Cache results:") + PrintTable(cache or {}) + end + + if cache then + + local metadata = {} + metadata.title = cache.title + metadata.duration = tonumber(cache.duration) + metadata.thumbnail = cache.thumbnail + + self:SetMetadata(metadata) + MediaPlayer.Metadata:Save(self) + + callback(self._metadata) + + else + + local fileId = self:GetGoogleDriveFileId() + local apiurl = MetadataUrl:format( fileId, APIKey ) + + self:Fetch( apiurl, + function( body, length, headers, code ) + OnReceiveMetadata( self, callback, body ) + end, + function( code ) + callback(false, "Failed to load YouTube ["..tostring(code).."]") + end + ) + + end +end diff --git a/lua/autorun/mediaplayer/services/googledrive/shared.lua b/lua/autorun/mediaplayer/services/googledrive/shared.lua new file mode 100644 index 0000000..29f9b20 --- /dev/null +++ b/lua/autorun/mediaplayer/services/googledrive/shared.lua @@ -0,0 +1,58 @@ +DEFINE_BASECLASS( "mp_service_base" ) + +SERVICE.Name = "Google Drive" +SERVICE.Id = "gd" +SERVICE.Base = "yt" + +local GdFileIdPattern = "[%a%d-_]+" +local UrlSchemes = { + "docs%.google%.com/file/d/" .. GdFileIdPattern .. "/", + "drive%.google%.com/file/d/" .. GdFileIdPattern .. "/" +} + +function SERVICE:New( url ) + local obj = BaseClass.New(self, url) + obj._data = obj:GetGoogleDriveFileId() + return obj +end + +function SERVICE:Match( url ) + for _, pattern in pairs(UrlSchemes) do + if string.find( url, pattern ) then + return true + end + end + return false +end + +function SERVICE:IsTimed() + return true +end + +function SERVICE:GetGoogleDriveFileId() + + local videoId + + if self.videoId then + + videoId = self.videoId + + elseif self.urlinfo then + + local url = self.urlinfo + + -- https://docs.google.com/file/d/(videoId) + if url.path and string.match(url.path, "^/file/d/([%a%d-_]+)") then + videoId = string.match(url.path, "^/file/d/([%a%d-_]+)") + end + + self.videoId = videoId + + end + + return videoId + +end + +-- Used for clientside inheritence of the YouTube service +SERVICE.GetYouTubeVideoId = GetGoogleDriveFileId diff --git a/lua/autorun/mediaplayer/services/html5_video.lua b/lua/autorun/mediaplayer/services/html5_video.lua new file mode 100644 index 0000000..c6521a8 --- /dev/null +++ b/lua/autorun/mediaplayer/services/html5_video.lua @@ -0,0 +1,57 @@ +SERVICE.Name = "HTML5 Video" +SERVICE.Id = "h5v" +SERVICE.Base = "res" + +SERVICE.FileExtensions = { + 'webm', + -- 'mp4', -- not yet supported by Awesomium + -- 'ogg' -- already registered as audio, need a work-around :( +} + +DEFINE_BASECLASS( "mp_service_base" ) + +if CLIENT then + + local MimeTypes = { + webm = "video/webm", + mp4 = "video/mp4", + ogg = "video/ogg" + } + + local EmbedHTML = [[ + +]] + + local JS_Volume = [[(function () { + var elem = document.getElementById('player'); + if (elem) { + elem.volume = %s; + } +}());]] + + function SERVICE:GetHTML() + local url = self.url + + local path = self.urlinfo.path + local ext = path:match("[^/]+%.(%S+)$") + + local mime = MimeTypes[ext] + + return EmbedHTML:format(url, mime) + end + + function SERVICE:Volume( volume ) + local origVolume = volume + + volume = BaseClass.Volume( self, volume ) + + if origVolume and ValidPanel( self.Browser ) then + self.Browser:RunJavascript(JS_Volume:format(volume)) + end + end + +end \ No newline at end of file diff --git a/lua/autorun/mediaplayer/services/image.lua b/lua/autorun/mediaplayer/services/image.lua new file mode 100644 index 0000000..ace7bb1 --- /dev/null +++ b/lua/autorun/mediaplayer/services/image.lua @@ -0,0 +1,23 @@ +SERVICE.Name = "Image" +SERVICE.Id = "img" +SERVICE.Base = "res" + +SERVICE.FileExtensions = { 'png', 'jpg', 'jpeg', 'gif' } + +if CLIENT then + + local EmbedHTML = [[ +
+
+]] + + function SERVICE:GetHTML() + return EmbedHTML:format( self.url ) + end + +end \ No newline at end of file diff --git a/lua/autorun/mediaplayer/services/jwplayer.lua b/lua/autorun/mediaplayer/services/jwplayer.lua new file mode 100644 index 0000000..4d95d38 --- /dev/null +++ b/lua/autorun/mediaplayer/services/jwplayer.lua @@ -0,0 +1,37 @@ +SERVICE.Name = "JWPlayer" +SERVICE.Id = "jw" +SERVICE.Base = "res" + +SERVICE.FileExtensions = { + 'mp4' +} + +if CLIENT then + + -- JWVideo + -- https://github.com/nexbr/playx/blob/master/lua/playx/client/handlers/default.lua + + -- playxlib.GenerateJWPlayer + -- https://github.com/nexbr/playx/blob/master/lua/playxlib.lua + + -- jwplayer + -- https://github.com/nexbr/playx/tree/gh-pages/js + + local EmbedHTML = [[ +

Not yet implemented

+]] + + function SERVICE:GetHTML() + local url = self.url + + local path = self.urlinfo.path + local ext = path:match("[^/]+%.(%S+)$") + + local mime = MimeTypes[ext] + + return EmbedHTML:format(url, mime) + end + + -- TODO: Sync/volume + +end \ No newline at end of file diff --git a/lua/autorun/mediaplayer/services/resource/cl_init.lua b/lua/autorun/mediaplayer/services/resource/cl_init.lua new file mode 100644 index 0000000..188ea25 --- /dev/null +++ b/lua/autorun/mediaplayer/services/resource/cl_init.lua @@ -0,0 +1,16 @@ +include "shared.lua" + +DEFINE_BASECLASS( "mp_service_browser" ) + +function SERVICE:OnBrowserReady( browser ) + BaseClass.OnBrowserReady( self, browser ) + + local html = self:GetHTML() + html = self.WrapHTML( html ) + + self.Browser:SetHTML( html ) +end + +function SERVICE:GetHTML() + return "

SERVICE.GetHTML not yet implemented

" +end diff --git a/lua/autorun/mediaplayer/services/resource/init.lua b/lua/autorun/mediaplayer/services/resource/init.lua new file mode 100644 index 0000000..eb24f11 --- /dev/null +++ b/lua/autorun/mediaplayer/services/resource/init.lua @@ -0,0 +1,35 @@ +AddCSLuaFile "shared.lua" +include "shared.lua" + +local urllib = url +local FilenamePattern = "([^/]+)%.%S+$" +local FilenameExtPattern = "([^/]+%.%S+)$" + +SERVICE.TitleIncludeExtension = true -- include extension in title + +function SERVICE:GetMetadata( callback ) + + if not self._metadata then + + local title + + local pattern = self.TitleIncludeExtension and + FilenameExtPattern or FilenamePattern + + local path = self.urlinfo.path + path = string.match( path, pattern ) -- get filename + + title = urllib.unescape( path ) + + self._metadata = { + title = title or self.Name, + url = self.url + } + + end + + if callback then + callback(self._metadata) + end + +end \ No newline at end of file diff --git a/lua/autorun/mediaplayer/services/resource/shared.lua b/lua/autorun/mediaplayer/services/resource/shared.lua new file mode 100644 index 0000000..77237dc --- /dev/null +++ b/lua/autorun/mediaplayer/services/resource/shared.lua @@ -0,0 +1,21 @@ +SERVICE.Name = "Resource" +SERVICE.Id = "res" +SERVICE.Base = "browser" +SERVICE.Abstract = true + +SERVICE.FileExtensions = {} + +function SERVICE:Match( url ) + -- check supported file extensions + for _, ext in pairs(self.FileExtensions) do + if url:find("([^/]+%." .. ext .. ")$") then + return true + end + end + + return false +end + +function SERVICE:IsTimed() + return false +end diff --git a/lua/autorun/mediaplayer/services/shoutcast.lua b/lua/autorun/mediaplayer/services/shoutcast.lua new file mode 100644 index 0000000..9643ba4 --- /dev/null +++ b/lua/autorun/mediaplayer/services/shoutcast.lua @@ -0,0 +1,15 @@ +SERVICE.Name = "SHOUTcast" +SERVICE.Id = "shc" +SERVICE.Base = "af" + +-- DEFINE_BASECLASS( "mp_service_af" ) + +local StationUrlPattern = "yp.shoutcast.com/sbin/tunein%-station%.pls%?id=%d+" + +function SERVICE:Match( url ) + return url:match( StationUrlPattern ) +end + +function SERVICE:IsTimed() + return false +end diff --git a/lua/autorun/mediaplayer/services/soundcloud/cl_init.lua b/lua/autorun/mediaplayer/services/soundcloud/cl_init.lua new file mode 100644 index 0000000..43fdd05 --- /dev/null +++ b/lua/autorun/mediaplayer/services/soundcloud/cl_init.lua @@ -0,0 +1 @@ +include "shared.lua" diff --git a/lua/autorun/mediaplayer/services/soundcloud/init.lua b/lua/autorun/mediaplayer/services/soundcloud/init.lua new file mode 100644 index 0000000..5eb6b0a --- /dev/null +++ b/lua/autorun/mediaplayer/services/soundcloud/init.lua @@ -0,0 +1,104 @@ +AddCSLuaFile "shared.lua" +include "shared.lua" + +local urllib = url + +local ClientId = MediaPlayer.GetConfigValue('soundcloud.client_id') + +-- http://developers.soundcloud.com/docs/api/reference +local MetadataUrl = { + resolve = "http://api.soundcloud.com/resolve.json?url=%s&client_id=" .. ClientId, + tracks = "" +} + +local function OnReceiveMetadata( self, callback, body ) + local resp = util.JSONToTable(body) + if not resp then + callback(false) + return + end + + if resp.errors then + callback(false, "The requested SoundCloud song wasn't found") + return + end + + local artist = resp.user and resp.user.username or "[Unknown artist]" + local stream = resp.stream_url + + if not stream then + callback(false, "The requested SoundCloud song doesn't allow streaming") + return + end + + -- http://developers.soundcloud.com/docs/api/reference#tracks + local metadata = {} + metadata.title = (resp.title or "[Unknown title]") .. " - " .. artist + metadata.duration = math.ceil(tonumber(resp.duration) / 1000) -- responds in ms + metadata.thumbnail = resp.artwork_url + + metadata.extra = { + stream = stream + } + + self:SetMetadata(metadata, true) + MediaPlayer.Metadata:Save(self) + + self.url = stream .. "?client_id=" .. ClientId + + callback(self._metadata) +end + +function SERVICE:GetMetadata( callback ) + if self._metadata then + callback( self._metadata ) + return + end + + local cache = MediaPlayer.Metadata:Query(self) + + if MediaPlayer.DEBUG then + print("MediaPlayer.GetMetadata Cache results:") + PrintTable(cache or {}) + end + + if cache then + + local metadata = {} + metadata.title = cache.title + metadata.duration = tonumber(cache.duration) + metadata.thumbnail = cache.thumbnail + + metadata.extra = cache.extra + + self:SetMetadata(metadata) + MediaPlayer.Metadata:Save(self) + + if metadata.extra then + local extra = util.JSONToTable(metadata.extra) + + if extra.stream then + self.url = tostring(extra.stream) .. "?client_id=" .. ClientId + end + end + + callback(self._metadata) + + else + + -- TODO: predetermine if we can skip the call to /resolve; check for + -- /track or /playlist in the url path. + + local apiurl = MetadataUrl.resolve:format( self.url ) + + self:Fetch( apiurl, + function( body, length, headers, code ) + OnReceiveMetadata( self, callback, body ) + end, + function( code ) + callback(false, "Failed to load YouTube ["..tostring(code).."]") + end + ) + + end +end diff --git a/lua/autorun/mediaplayer/services/soundcloud/shared.lua b/lua/autorun/mediaplayer/services/soundcloud/shared.lua new file mode 100644 index 0000000..17047b0 --- /dev/null +++ b/lua/autorun/mediaplayer/services/soundcloud/shared.lua @@ -0,0 +1,20 @@ +DEFINE_BASECLASS( "mp_service_base" ) + +SERVICE.Name = "SoundCloud" +SERVICE.Id = "sc" +SERVICE.Base = "af" + +SERVICE.PrefetchMetadata = false + +function SERVICE:New( url ) + local obj = BaseClass.New(self, url) + + -- TODO: grab id from /tracks/:id, etc. + obj._data = obj.urlinfo.path or '0' + + return obj +end + +function SERVICE:Match( url ) + return string.match( url, "soundcloud.com" ) +end diff --git a/lua/autorun/mediaplayer/services/twitch/cl_init.lua b/lua/autorun/mediaplayer/services/twitch/cl_init.lua new file mode 100644 index 0000000..926c6d8 --- /dev/null +++ b/lua/autorun/mediaplayer/services/twitch/cl_init.lua @@ -0,0 +1,56 @@ +include "shared.lua" + +DEFINE_BASECLASS( "mp_service_browser" ) + +local TwitchUrl = "http://www.twitch.tv/%s/%s/%s/popout" + +--- +-- Approximate amount of time it takes for the Twitch video player to load upon +-- loading the webpage. +-- +local playerLoadDelay = 5 + +local secMinute = 60 +local secHour = secMinute * 60 + +local function formatTwitchTime( seconds ) + local hours = math.floor((seconds / secHour) % 24) + local minutes = math.floor((seconds / secMinute) % 60) + seconds = math.floor(seconds % 60) + + local tbl = {} + + if hours > 0 then + table.insert(tbl, hours) + table.insert(tbl, 'h') + end + + if hours > 0 or minutes > 0 then + table.insert(tbl, minutes) + table.insert(tbl, 'm') + end + + table.insert(tbl, seconds) + table.insert(tbl, 's') + + return table.concat(tbl, '') +end + +function SERVICE:OnBrowserReady( browser ) + + BaseClass.OnBrowserReady( self, browser ) + + local info = self:GetTwitchVideoInfo() + local url = TwitchUrl:format(info.channel, info.type, info.chapterId) + + -- Move current time forward due to twitch player load time + local curTime = math.min( self:CurrentTime() + playerLoadDelay, self:Duration() ) + + local time = math.ceil( curTime ) + if time > 5 then + url = url .. '?t=' .. formatTwitchTime(time) + end + + browser:OpenURL( url ) + +end diff --git a/lua/autorun/mediaplayer/services/twitch/init.lua b/lua/autorun/mediaplayer/services/twitch/init.lua new file mode 100644 index 0000000..4aa56cf --- /dev/null +++ b/lua/autorun/mediaplayer/services/twitch/init.lua @@ -0,0 +1,92 @@ +AddCSLuaFile "shared.lua" +include "shared.lua" + +local urllib = url + +local MetadataUrl = "https://api.twitch.tv/kraken/videos/%s" + +local function OnReceiveMetadata( self, callback, body ) + + local metadata = {} + + local response = util.JSONToTable( body ) + if not response then + callback(false) + return + end + + -- Stream invalid + if response.status and response.status == 404 then + return callback( false, "Twitch.TV: " .. tostring(response.message) ) + end + + metadata.title = response.title + metadata.duration = response.length + + -- Add 30 seconds to accomodate for ads in video over 5 minutes + local duration = tonumber(metadata.duration) + if duration and duration > ( 60 * 5 ) then + metadata.duration = duration + 30 + end + + metadata.thumbnail = response.preview + + self:SetMetadata(metadata, true) + MediaPlayer.Metadata:Save(self) + + callback(self._metadata) + +end + +function SERVICE:GetMetadata( callback ) + if self._metadata then + callback( self._metadata ) + return + end + + local cache = MediaPlayer.Metadata:Query(self) + + if MediaPlayer.DEBUG then + print("MediaPlayer.GetMetadata Cache results:") + PrintTable(cache or {}) + end + + if cache then + + local metadata = {} + metadata.title = cache.title + metadata.duration = cache.duration + metadata.thumbnail = cache.thumbnail + + self:SetMetadata(metadata) + MediaPlayer.Metadata:Save(self) + + callback(self._metadata) + + else + + local info = self:GetTwitchVideoInfo() + + -- API call fix + if info.type == 'b' then + info.type = 'a' + end + + local apiurl = MetadataUrl:format( info.type .. info.chapterId ) + + self:Fetch( apiurl, + function( body, length, headers, code ) + OnReceiveMetadata( self, callback, body ) + end, + function( code ) + callback(false, "Failed to load Twitch.TV ["..tostring(code).."]") + end, + + -- Twitch.TV API v3 headers + { + ["Accept"] = "application/vnd.twitchtv.v3+json" + } + ) + + end +end diff --git a/lua/autorun/mediaplayer/services/twitch/shared.lua b/lua/autorun/mediaplayer/services/twitch/shared.lua new file mode 100644 index 0000000..9b6a161 --- /dev/null +++ b/lua/autorun/mediaplayer/services/twitch/shared.lua @@ -0,0 +1,54 @@ +DEFINE_BASECLASS( "mp_service_base" ) + +SERVICE.Name = "Twitch.TV - Video" +SERVICE.Id = "twv" +SERVICE.Base = "browser" + +function SERVICE:New( url ) + local obj = BaseClass.New(self, url) + + local info = obj:GetTwitchVideoInfo() + obj._data = info.channel .. "_" .. info.chapterId + + return obj +end + +function SERVICE:Match( url ) + -- TODO: should the parsed url be passed instead? + return (string.match(url, "justin.tv") or + string.match(url, "twitch.tv")) and + string.match(url, ".tv/[%w_]+/%a/%d+") +end + +function SERVICE:GetTwitchVideoInfo() + + local info + + if self._twitchInfo then + + info = self._twitchInfo + + elseif self.urlinfo then + + local url = self.urlinfo + + local channel, type, chapterId = string.match(url.path, "^/([%w_]+)/(%a)/(%d+)") + + -- Chapter videos use /c/ while archived videos use /b/ + if type ~= "c" then + type = "b" + end + + info = { + channel = channel, + type = type, + chapterId = chapterId + } + + self._twitchInfo = info + + end + + return info + +end diff --git a/lua/autorun/mediaplayer/services/twitchstream/cl_init.lua b/lua/autorun/mediaplayer/services/twitchstream/cl_init.lua new file mode 100644 index 0000000..9de8ffa --- /dev/null +++ b/lua/autorun/mediaplayer/services/twitchstream/cl_init.lua @@ -0,0 +1,23 @@ +include "shared.lua" + +DEFINE_BASECLASS( "mp_service_browser" ) + +local TwitchUrl = "http://www.twitch.tv/%s/popout" + +local JS_HideControls = [[ +document.body.style.cssText = 'overflow:hidden;height:106.8% !important';]] + +function SERVICE:OnBrowserReady( browser ) + + BaseClass.OnBrowserReady( self, browser ) + + local channel = self:GetTwitchChannel() + local url = TwitchUrl:format(channel) + + browser:OpenURL( url ) + + browser.OnFinishLoading = function(self) + self:QueueJavascript(JS_HideControls) + end + +end diff --git a/lua/autorun/mediaplayer/services/twitchstream/init.lua b/lua/autorun/mediaplayer/services/twitchstream/init.lua new file mode 100644 index 0000000..c1eb4b0 --- /dev/null +++ b/lua/autorun/mediaplayer/services/twitchstream/init.lua @@ -0,0 +1,61 @@ +AddCSLuaFile "shared.lua" +include "shared.lua" + +local urllib = url + +local MetadataUrl = "https://api.twitch.tv/kraken/streams/%s" + +local function OnReceiveMetadata( self, callback, body ) + + local metadata = {} + + local response = util.JSONToTable( body ) + if not response then + callback(false) + return + end + + local stream = response.stream + + -- Stream offline + if not stream then + return callback( false, "Twitch.TV: The requested stream was offline" ) + end + + local channel = stream.channel + local status = channel and channel.status or "Twitch.TV Stream" + + metadata.title = status + metadata.thumbnail = stream.preview.medium + + self:SetMetadata(metadata, true) + + callback(self._metadata) + +end + +function SERVICE:GetMetadata( callback ) + + if self._metadata then + callback( self._metadata ) + return + end + + local channel = self:GetTwitchChannel() + local apiurl = MetadataUrl:format( channel ) + + self:Fetch( apiurl, + function( body, length, headers, code ) + OnReceiveMetadata( self, callback, body ) + end, + function( code ) + callback(false, "Failed to load Twitch.TV ["..tostring(code).."]") + end, + + -- Twitch.TV API v3 headers + { + ["Accept"] = "application/vnd.twitchtv.v3+json" + } + ) + +end \ No newline at end of file diff --git a/lua/autorun/mediaplayer/services/twitchstream/shared.lua b/lua/autorun/mediaplayer/services/twitchstream/shared.lua new file mode 100644 index 0000000..621e6e4 --- /dev/null +++ b/lua/autorun/mediaplayer/services/twitchstream/shared.lua @@ -0,0 +1,44 @@ +DEFINE_BASECLASS( "mp_service_browser" ) + +SERVICE.Name = "Twitch.TV - Stream" +SERVICE.Id = "twl" +SERVICE.Base = "browser" + +function SERVICE:New( url ) + local obj = BaseClass.New(self, url) + + local channel = obj:GetTwitchChannel() + obj._data = channel + + return obj +end + +function SERVICE:Match( url ) + return string.match(url, "twitch.tv") and + string.match(url, ".tv/[%w_]+$") +end + +function SERVICE:IsTimed() + return false +end + +function SERVICE:GetTwitchChannel() + + local channel + + if self._twitchChannel then + + channel = self._twitchChannel + + elseif self.urlinfo then + + local url = self.urlinfo + + channel = string.match(url.path, "^/([%w_]+)") + self._twitchChannel = channel + + end + + return channel + +end diff --git a/lua/autorun/mediaplayer/services/vimeo/cl_init.lua b/lua/autorun/mediaplayer/services/vimeo/cl_init.lua new file mode 100644 index 0000000..c3f0baf --- /dev/null +++ b/lua/autorun/mediaplayer/services/vimeo/cl_init.lua @@ -0,0 +1,49 @@ +include "shared.lua" + +DEFINE_BASECLASS( "mp_service_browser" ) + +local JS_SetVolume = "if(window.MediaPlayer) MediaPlayer.setVolume(%s);" +local JS_Seek = "if(window.MediaPlayer) MediaPlayer.seek(%s);" + +local function VimeoSetVolume( self ) + if not self.Browser then return end + local js = JS_SetVolume:format( MediaPlayer.Volume() ) + self.Browser:RunJavascript(js) +end + +local function VimeoSeek( self, seekTime ) + if not self.Browser then return end + local js = JS_Seek:format( seekTime ) + self.Browser:RunJavascript(js) +end + +function SERVICE:SetVolume( volume ) + VimeoSetVolume( self ) +end + +function SERVICE:OnBrowserReady( browser ) + + BaseClass.OnBrowserReady( self, browser ) + + local videoId = self:GetVimeoVideoId() + + -- local url = VimeoVideoUrl:format( videoId ) + -- browser:OpenURL( url ) + + -- browser:QueueJavascript( JS_Init ) + + -- local html = EmbedHTML:format( videoId ) + -- html = self.WrapHTML( html ) + -- browser:SetHTML( html ) + + local url = "http://localhost/vimeo.html#" .. videoId + browser:OpenURL( url ) + +end + +function SERVICE:Sync() + local seekTime = self:CurrentTime() + if seekTime > 0 then + VimeoSeek( self, seekTime ) + end +end diff --git a/lua/autorun/mediaplayer/services/vimeo/init.lua b/lua/autorun/mediaplayer/services/vimeo/init.lua new file mode 100644 index 0000000..1681936 --- /dev/null +++ b/lua/autorun/mediaplayer/services/vimeo/init.lua @@ -0,0 +1,68 @@ +AddCSLuaFile "shared.lua" +include "shared.lua" + +local MetadataUrl = "http://vimeo.com/api/v2/video/%s.json" + +local function OnReceiveMetadata( self, callback, body ) + + local metadata = {} + + local data = util.JSONToTable( body ) + if not data then + return callback( false, "Failed to parse video's metadata response." ) + end + + data = data[1] + + metadata.title = data.title + metadata.duration = data.duration + metadata.thumbnail = data.thumbnail_medium + + self:SetMetadata(metadata, true) + MediaPlayer.Metadata:Save(self) + + callback(self._metadata) + +end + +function SERVICE:GetMetadata( callback ) + if self._metadata then + callback( self._metadata ) + return + end + + local cache = MediaPlayer.Metadata:Query(self) + + if MediaPlayer.DEBUG then + print("MediaPlayer.GetMetadata Cache results:") + PrintTable(cache or {}) + end + + if cache then + + local metadata = {} + metadata.title = cache.title + metadata.duration = cache.duration + metadata.thumbnail = cache.thumbnail + + self:SetMetadata(metadata) + MediaPlayer.Metadata:Save(self) + + callback(self._metadata) + + else + + local videoId = self:GetVimeoVideoId() + local apiurl = MetadataUrl:format( videoId ) + + self:Fetch( apiurl, + function( body, length, headers, code ) + OnReceiveMetadata( self, callback, body ) + end, + function( code ) + callback(false, "Failed to load Vimeo ["..tostring(code).."]") + end + ) + + end +end diff --git a/lua/autorun/mediaplayer/services/vimeo/shared.lua b/lua/autorun/mediaplayer/services/vimeo/shared.lua new file mode 100644 index 0000000..9d51855 --- /dev/null +++ b/lua/autorun/mediaplayer/services/vimeo/shared.lua @@ -0,0 +1,38 @@ +DEFINE_BASECLASS( "mp_service_browser" ) + +SERVICE.Name = "Vimeo" +SERVICE.Id = "vm" +SERVICE.Base = "browser" + +function SERVICE:New( url ) + local obj = BaseClass.New(self, url) + obj._data = obj:GetVimeoVideoId() + return obj +end + +function SERVICE:Match( url ) + return string.find( url, "vimeo.com/%d+" ) +end + +function SERVICE:GetVimeoVideoId() + + local videoId + + if self.videoId then + + videoId = self.videoId + + elseif self.urlinfo then + + local url = self.urlinfo + + -- http://www.vimeo.com/(videoId) + videoId = string.match(url.path, "^/(%d+)") + + self.videoId = videoId + + end + + return videoId + +end diff --git a/lua/autorun/mediaplayer/services/youtube/cl_init.lua b/lua/autorun/mediaplayer/services/youtube/cl_init.lua new file mode 100644 index 0000000..2b12928 --- /dev/null +++ b/lua/autorun/mediaplayer/services/youtube/cl_init.lua @@ -0,0 +1,179 @@ +include "shared.lua" + +local urllib = url + +DEFINE_BASECLASS( "mp_service_browser" ) + +-- https://developers.google.com/youtube/player_parameters +-- TODO: add closed caption option according to cvar +SERVICE.VideoUrlFormat = "https://www.youtube.com/embed/%s?enablejsapi=1&version=3&playerapiid=ytplayer&autoplay=1&controls=0&modestbranding=1&rel=0&showinfo=0" + +-- YouTube player API +-- https://developers.google.com/youtube/js_api_reference +local JS_Init = [[ +if (window.MediaPlayer === undefined) { + window.MediaPlayer = (function(){ + var playerId = '%s', + timed = %s; + + return { + + init: function () { + if (this.player) return; + this.player = document.getElementById(playerId); + if (!this.player) { + console.error('Unable to find YouTube player element!'); + return false; + } + }, + + isPlayerReady: function () { + return ( this.ytReady || + (this.player && (this.player.setVolume !== undefined)) ); + }, + + setVolume: function ( volume ) { + if (!this.isPlayerReady()) return; + this.player.setVolume(volume); + }, + + play: function () { + if (!this.isPlayerReady()) return; + this.player.playVideo(); + }, + + pause: function () { + if (!this.isPlayerReady()) return; + this.player.pauseVideo(); + }, + + seek: function ( seekTime ) { + if (!this.isPlayerReady()) return; + if (!timed) return; + + var state, curTime, duration, diffTime; + + state = this.player.getPlayerState(); + + /*if (state < 0) { + this.player.playVideo(); + }*/ + + if (state === 3) return; + + duration = this.player.getDuration(); + if (seekTime > duration) return; + + curTime = this.player.getCurrentTime(); + diffTime = Math.abs(curTime - seekTime); + if (diffTime < 5) return; + + this.player.seekTo(seekTime, true); + } + + }; + })(); + + MediaPlayer.init(); +} + +window.onYouTubePlayerReady = function (playerId) { + MediaPlayer.ytReady = true; + MediaPlayer.init(); +}; +]] + +local JS_SetVolume = "if(window.MediaPlayer) MediaPlayer.setVolume(%s);" +local JS_Seek = "if(window.MediaPlayer) MediaPlayer.seek(%s);" +local JS_Play = "if(window.MediaPlayer) MediaPlayer.play();" +local JS_Pause = "if(window.MediaPlayer) MediaPlayer.pause();" + +local function YTSetVolume( self ) + -- if not self.playerId then return end + local js = JS_SetVolume:format( MediaPlayer.Volume() * 100 ) + if self.Browser then + self.Browser:RunJavascript(js) + end +end + +local function YTSeek( self, seekTime ) + -- if not self.playerId then return end + local js = JS_Seek:format( seekTime ) + if self.Browser then + self.Browser:RunJavascript(js) + end +end + +function SERVICE:SetVolume( volume ) + local js = JS_SetVolume:format( MediaPlayer.Volume() * 100 ) + self.Browser:RunJavascript(js) +end + +function SERVICE:OnBrowserReady( browser ) + + BaseClass.OnBrowserReady( self, browser ) + + -- Resume paused player + if self._YTPaused then + self.Browser:RunJavascript( JS_Play ) + self._YTPaused = nil + return + end + + if not self._setupBrowser then + + -- This doesn't always get called in time, but it's a nice fallback + browser:AddFunction( "window", "onYouTubePlayerReady", function( playerId ) + if not playerId then return end + self.playerId = string.JavascriptSafe( playerId ) + + -- Initialize JavaScript MediaPlayer interface + local jsinit = JS_Init:format( self.playerId, self:IsTimed() ) + browser:RunJavascript( jsinit ) + + YTSetVolume( self ) + end ) + + self._setupBrowser = true + + end + + local videoId = self:GetYouTubeVideoId() + local url = self.VideoUrlFormat:format( videoId ) + local curTime = self:CurrentTime() + + -- Add start time to URL if the video didn't just begin + if self:IsTimed() and curTime > 3 then + url = url .. "&start=" .. math.Round(curTime) + end + + -- Trick the embed page into thinking the referrer is youtube.com; allows + -- playing some restricted content due to the block by default behavior + -- described here: http://stackoverflow.com/a/13463245/1490006 + url = urllib.escape(url) + url = "http://www.gmtower.org/apps/mediaplayer/redirect.html#" .. url + + browser:OpenURL(url) + + -- Initialize JavaScript MediaPlayer interface + local playerId = "player1" + local jsinit = JS_Init:format( playerId, self:IsTimed() ) + browser:QueueJavascript( jsinit ) + +end + +function SERVICE:Pause() + BaseClass.Pause( self ) + + if ValidPanel(self.Browser) then + self.Browser:RunJavascript(JS_Pause) + self._YTPaused = true + end +end + +function SERVICE:Sync() + local seekTime = self:CurrentTime() + if seekTime > 0 then + YTSeek( self, seekTime ) + end +end diff --git a/lua/autorun/mediaplayer/services/youtube/init.lua b/lua/autorun/mediaplayer/services/youtube/init.lua new file mode 100644 index 0000000..2f4382d --- /dev/null +++ b/lua/autorun/mediaplayer/services/youtube/init.lua @@ -0,0 +1,149 @@ +AddCSLuaFile "shared.lua" +include "shared.lua" + +-- https://developers.google.com/youtube/v3/ +local APIKey = MediaPlayer.GetConfigValue('google.api_key') +local MetadataUrl = "https://www.googleapis.com/youtube/v3/videos?id=%s&key=%s&type=video&part=contentDetails,snippet,status&videoEmbeddable=true&videoSyndicated=true" + +--- +-- Helper function for converting ISO 8601 time strings; this is the formatting +-- used for duration specified in the YouTube v3 API. +-- +-- http://stackoverflow.com/a/22149575/1490006 +-- +local function convertISO8601Time( duration ) + local a = {} + + for part in string.gmatch(duration, "%d+") do + table.insert(a, part) + end + + if duration:find('M') and not (duration:find('H') or duration:find('S')) then + a = {0, a[1], 0} + end + + if duration:find('H') and not duration:find('M') then + a = {a[1], 0, a[2]} + end + + if duration:find('H') and not (duration:find('M') or duration:find('S')) then + a = {a[1], 0, 0} + end + + duration = 0 + + if #a == 3 then + duration = duration + tonumber(a[1]) * 3600 + duration = duration + tonumber(a[2]) * 60 + duration = duration + tonumber(a[3]) + end + + if #a == 2 then + duration = duration + tonumber(a[1]) * 60 + duration = duration + tonumber(a[2]) + end + + if #a == 1 then + duration = duration + tonumber(a[1]) + end + + return duration +end + +local function OnReceiveMetadata( self, callback, body ) + + local metadata = {} + + -- Check for valid JSON response + local resp = util.JSONToTable( body ) + if not resp then + return callback(false) + end + + -- If 'error' key is present, the query failed. + if resp.error then + return callback(false, table.Lookup(resp, 'error.message')) + end + + -- We need at least one result + local results = table.Lookup(resp, 'pageInfo.totalResults') + if not ( results and results > 0 ) then + return callback(false, "Requested video wasn't found") + end + + local item = resp.items[1] + + -- Video must be embeddable + if not table.Lookup(item, 'status.embeddable') then + return callback( false, "Requested video was embed disabled" ) + end + + metadata.title = table.Lookup(item, 'snippet.title') + + -- Check for live broadcast + local liveBroadcast = table.Lookup(item, 'snippet.liveBroadcastContent') + if liveBroadcast == 'none' then + -- Duration is an ISO 8601 string + local durationStr = table.Lookup(item, 'contentDetails.duration') + metadata.duration = math.max(1, convertISO8601Time(durationStr)) + else + metadata.duration = 0 -- mark as live video + end + + -- 'medium' size thumbnail doesn't have letterboxing + metadata.thumbnail = table.Lookup(item, 'snippet.thumbnails.medium.url') + + self:SetMetadata(metadata, true) + + if self:IsTimed() then + MediaPlayer.Metadata:Save(self) + end + + callback(self._metadata) + +end + +function SERVICE:GetMetadata( callback ) + if self._metadata then + callback( self._metadata ) + return + end + + local cache = MediaPlayer.Metadata:Query(self) + + if MediaPlayer.DEBUG then + print("MediaPlayer.GetMetadata Cache results:") + PrintTable(cache or {}) + end + + if cache then + + local metadata = {} + metadata.title = cache.title + metadata.duration = tonumber(cache.duration) + metadata.thumbnail = cache.thumbnail + + self:SetMetadata(metadata) + + if self:IsTimed() then + MediaPlayer.Metadata:Save(self) + end + + callback(self._metadata) + + else + + local videoId = self:GetYouTubeVideoId() + local apiurl = MetadataUrl:format( videoId, APIKey ) + + self:Fetch( apiurl, + function( body, length, headers, code ) + OnReceiveMetadata( self, callback, body ) + end, + function( code ) + callback(false, "Failed to load YouTube ["..tostring(code).."]") + end + ) + + end +end diff --git a/lua/autorun/mediaplayer/services/youtube/shared.lua b/lua/autorun/mediaplayer/services/youtube/shared.lua new file mode 100644 index 0000000..6cdb059 --- /dev/null +++ b/lua/autorun/mediaplayer/services/youtube/shared.lua @@ -0,0 +1,79 @@ +DEFINE_BASECLASS( "mp_service_base" ) + +SERVICE.Name = "YouTube" +SERVICE.Id = "yt" +SERVICE.Base = "browser" + +local YtVideoIdPattern = "[%a%d-_]+" +local UrlSchemes = { + "youtube%.com/watch%?v=" .. YtVideoIdPattern, + "youtu%.be/watch%?v=" .. YtVideoIdPattern, + "youtube%.com/v/" .. YtVideoIdPattern, + "youtu%.be/v/" .. YtVideoIdPattern, + "youtube%.googleapis%.com/v/" .. YtVideoIdPattern +} + +function SERVICE:New( url ) + local obj = BaseClass.New(self, url) + obj._data = obj:GetYouTubeVideoId() + return obj +end + +function SERVICE:Match( url ) + for _, pattern in pairs(UrlSchemes) do + if string.find( url, pattern ) then + return true + end + end + + return false +end + +function SERVICE:IsTimed() + if self._istimed == nil then + -- YouTube Live resolves to 0 second video duration + self._istimed = self:Duration() > 0 + end + + return self._istimed +end + +function SERVICE:GetYouTubeVideoId() + + local videoId + + if self.videoId then + + videoId = self.videoId + + elseif self.urlinfo then + + local url = self.urlinfo + + -- http://www.youtube.com/watch?v=(videoId) + if url.query and url.query.v then + videoId = url.query.v + + -- http://www.youtube.com/v/(videoId) + elseif url.path and string.match(url.path, "^/v/([%a%d-_]+)") then + videoId = string.match(url.path, "^/v/([%a%d-_]+)") + + -- http://youtube.googleapis.com/v/(videoId) + elseif url.path and string.match(url.path, "^/v/([%a%d-_]+)") then + videoId = string.match(url.path, "^/v/([%a%d-_]+)") + + -- http://youtu.be/(videoId) + elseif string.match(url.host, "youtu.be") and + url.path and string.match(url.path, "^/([%a%d-_]+)$") and + ( (not url.query) or #url.query == 0 ) then -- short url + + videoId = string.match(url.path, "^/([%a%d-_]+)$") + end + + self.videoId = videoId + + end + + return videoId + +end diff --git a/lua/autorun/mediaplayer/sh_history.lua b/lua/autorun/mediaplayer/sh_history.lua new file mode 100644 index 0000000..aa6c032 --- /dev/null +++ b/lua/autorun/mediaplayer/sh_history.lua @@ -0,0 +1,105 @@ +--[[--------------------------------------------------------- + Media Player History +-----------------------------------------------------------]] + +MediaPlayer.History = {} + +--- +-- Default metadata table name +-- @type String +-- +local TableName = "mediaplayer_history" + +--- +-- SQLite table struct +-- @type String +-- +local TableStruct = string.format([[ +CREATE TABLE %s ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + mediaid VARCHAR(48), + url VARCHAR(512), + player_name VARCHAR(32), + steamid VARCHAR(32), + time DATETIME DEFAULT CURRENT_TIMESTAMP +)]], TableName) + +--- +-- Default number of results to return +-- @type Integer +-- +local DefaultResultLimit = 100 + +--- +-- Log the given media as a request. +-- +-- @param media Media service object. +-- @return table SQL query results. +-- +function MediaPlayer.History:LogRequest( media ) + local id = media:UniqueID() + if not id then return end + + local ply = media:GetOwner() + if not IsValid(ply) then return end + + local query = string.format( "INSERT INTO `%s` " .. + "(mediaid,url,player_name,steamid) " .. + "VALUES ('%s',%s,%s,'%s')", + TableName, + media:UniqueID(), + sql.SQLStr( media:Url() ), + sql.SQLStr( ply:Nick() ), + ply:SteamID64() ) + + local result = sql.Query(query) + + if MediaPlayer.DEBUG then + print("MediaPlayer.History.LogRequest") + print(query) + if istable(result) then + PrintTable(result) + else + print(result) + end + end + + return result +end + +function MediaPlayer.History:GetRequestsByPlayer( ply, limit ) + if not isnumber(limit) then + limit = DefaultResultLimit + end + + local query = string.format( [[ +SELECT H.*, M.title, M.thumbnail, M.duration +FROM %s AS H +JOIN mediaplayer_metadata AS M + ON (M.id = H.mediaid) +WHERE steamid='%s' +LIMIT %d]], + TableName, + ply:SteamID64(), + limit ) + + local result = sql.Query(query) + + if MediaPlayer.DEBUG then + print("MediaPlayer.History.GetRequestsByPlayer", ply, limit) + print(query) + if istable(result) then + PrintTable(result) + else + print(result) + end + end + + return result +end + +-- Create the SQLite table if it doesn't exist +if not sql.TableExists(TableName) then + Msg("MediaPlayer.History: Creating `" .. TableName .. "` table...\n") + print(sql.Query(TableStruct)) +end diff --git a/lua/autorun/mediaplayer/sh_mediaplayer.lua b/lua/autorun/mediaplayer/sh_mediaplayer.lua new file mode 100644 index 0000000..c9cde25 --- /dev/null +++ b/lua/autorun/mediaplayer/sh_mediaplayer.lua @@ -0,0 +1,198 @@ +--[[--------------------------------------------------------- + Media Player Types +-----------------------------------------------------------]] + +MediaPlayer.Type = {} + +--- +-- Registers a media player type. +-- +-- @param tbl Media player type table. +-- +function MediaPlayer.Register( tbl ) + + local name = tbl.Name + + if not name then + ErrorNoHalt("MediaPlayer.Register - Must include name property\n") + debug.Trace() + return + end + + name = name:lower() -- always use lowercase names + tbl.Name = name + tbl.__index = tbl + + -- Set base meta table + local base = tbl.Base or "base" + if base and name ~= "base" then + base = base:lower() + + if not MediaPlayer.Type[base] then + ErrorNoHalt("MediaPlayer.Register - Invalid base name: " .. base .. "\n") + debug.Trace() + return + end + + base = MediaPlayer.Type[base] + + setmetatable(tbl, { + __index = base, + __tostring = base.__tostring + }) + end + + local classname = "mp_" .. name + + -- Store media player type as a base class + baseclass.Set( classname, tbl ) + + -- Save player type + MediaPlayer.Type[name] = tbl + + if MediaPlayer.DEBUG then + Msg( "MediaPlayer.Register\t" .. name .. "\n" ) + end + +end + +function MediaPlayer.IsValidType( type ) + return MediaPlayer.Type[type] ~= nil +end + +-- Load players +do + local path = "players/" + local players = { + "base", -- MUST LOAD FIRST! + "entity" + } + + for _, player in ipairs(players) do + local clfile = path .. player .. "/cl_init.lua" + local svfile = path .. player .. "/init.lua" + + MEDIAPLAYER = {} + + if SERVER then + AddCSLuaFile(clfile) + include(svfile) + else + include(clfile) + end + + MediaPlayer.Register( MEDIAPLAYER ) + MEDIAPLAYER = nil + end +end + + +--[[--------------------------------------------------------- + Media Player Helpers +-----------------------------------------------------------]] + +MediaPlayer.List = MediaPlayer.List or {} +MediaPlayer._count = MediaPlayer._count or 0 + +--- +-- Creates a media player object. +-- +-- @param id Media player ID. +-- @param type? Media player type (defaults to 'base'). +-- @return table Media player object. +-- +function MediaPlayer.Create( id, type ) + -- Inherit media player type + local PlayerType = MediaPlayer.Type[type] + PlayerType = PlayerType or MediaPlayer.Type.base + + -- Create media player object + local mp = setmetatable( {}, { __index = PlayerType } ) + + -- Assign unique ID + if id then + mp.id = id + elseif SERVER then + MediaPlayer._count = MediaPlayer._count + 1 + mp.id = MediaPlayer._count + else + mp.id = id or -1 + end + + mp:Init() + + -- Add to media player list + MediaPlayer.List[mp.id] = mp + + if MediaPlayer.DEBUG then + print( "Created Media Player", mp, mp.Name, type ) + end + + return mp +end + +--- +-- Destroys the given media player object. +-- +-- @param table Media player object. +-- +function MediaPlayer.Destroy( mp ) + -- TODO: does this need anything else? + MediaPlayer.List[mp.id] = nil + + if MediaPlayer.DEBUG then + print( "Destroyed Media Player '" .. tostring(mp.id) .. "'" ) + end +end + +--- +-- Gets the media player associated with the given ID. +-- +-- @param id Media player ID. +-- @return table Media player object. +-- +function MediaPlayer.GetById( id ) + local mp = MediaPlayer.List[id] + if mp then + return mp + else + -- Since entity indexes can change, let's iterate the list just to + -- be sure... + for _, mp in pairs(MediaPlayer.List) do + if mp:GetId() == id then + return mp + end + end + end +end + +--- +-- Gets all active media players. +-- +-- @return table Array of all active media players. +-- +function MediaPlayer.GetAll() + return MediaPlayer.List +end + + +--[[--------------------------------------------------------- + Media Player Think Loop +-----------------------------------------------------------]] + +MediaPlayer.ThinkInterval = 0.2 -- seconds + +local function MediaPlayerThink() + for id, mp in pairs( MediaPlayer.List ) do + mp:Think() + end +end + +if timer.Exists( "MediaPlayerThink" ) then + timer.Destroy( "MediaPlayerThink" ) +end + +-- TODO: only start timer when at least one mediaplayer is created; stop it when +-- there are none left. +timer.Create( "MediaPlayerThink", MediaPlayer.ThinkInterval, 0, MediaPlayerThink ) +timer.Start( "MediaPlayerThink" ) diff --git a/lua/autorun/mediaplayer/sh_services.lua b/lua/autorun/mediaplayer/sh_services.lua new file mode 100644 index 0000000..c0fcff2 --- /dev/null +++ b/lua/autorun/mediaplayer/sh_services.lua @@ -0,0 +1,127 @@ +MediaPlayer.Services = {} + +function MediaPlayer.RegisterService( service ) + + local base + + if service.Base then + base = MediaPlayer.Services[service.Base] + elseif MediaPlayer.Services.base then + base = MediaPlayer.Services.base + end + + -- Inherit base service + setmetatable( service, { __index = base } ) + + -- Create base class for service + baseclass.Set( "mp_service_" .. service.Id, service ) + + -- Store service + MediaPlayer.Services[ service.Id ] = service + + if MediaPlayer.DEBUG then + print( "MediaPlayer.RegisterService", service.Name ) + end + +end + +function MediaPlayer.GetValidServiceNames( whitelist ) + local tbl = {} + + for _, service in pairs(MediaPlayer.Services) do + if not rawget(service, "Abstract") then + if whitelist then + if table.HasValue( whitelist, service.Id ) then + table.insert( tbl, service.Name ) + end + else + table.insert( tbl, service.Name ) + end + end + end + + return tbl +end + +function MediaPlayer.ValidUrl( url ) + + for id, service in pairs(MediaPlayer.Services) do + if service:Match( url ) then + return true + end + end + + return false + +end + +function MediaPlayer.GetMediaForUrl( url ) + + local service + + for id, s in pairs(MediaPlayer.Services) do + if s:Match( url ) then + service = s + break + end + end + + if not service then + service = MediaPlayer.Services.base + end + + return service:New( url ) + +end + +-- Load services +do + local path = "services/" + + local fullpath = "autorun/mediaplayer/" .. path + + local services = { + "base", -- MUST LOAD FIRST! + + -- Browser + "browser", -- base + "youtube", + "googledrive", + "twitch", + "twitchstream", + "vimeo", + + -- HTML Resources + "resource", -- base + "image", + "html5_video", + + -- IGModAudioChannel + "audiofile", + "shoutcast", + "soundcloud" + } + + for _, name in ipairs(services) do + local clfile = path .. name .. "/cl_init.lua" + local svfile = path .. name .. "/init.lua" + local shfile = fullpath .. name .. ".lua" + + if file.Exists(shfile, "LUA") then + clfile = shfile + svfile = shfile + end + + SERVICE = {} + + if SERVER then + AddCSLuaFile(clfile) + include(svfile) + else + include(clfile) + end + + MediaPlayer.RegisterService( SERVICE ) + SERVICE = nil + end +end diff --git a/lua/autorun/mediaplayer/shared.lua b/lua/autorun/mediaplayer/shared.lua new file mode 100644 index 0000000..0cdc104 --- /dev/null +++ b/lua/autorun/mediaplayer/shared.lua @@ -0,0 +1,78 @@ +MediaPlayer = MediaPlayer or {} + +--[[--------------------------------------------------------- + ConVars +-----------------------------------------------------------]] + +MediaPlayer.Cvars = {} +MediaPlayer.Cvars.Debug = CreateConVar( "mediaplayer_debug", 0, {FCVAR_ARCHIVE,FCVAR_DONTRECORD}, "Enables media player debug mode; logs a bunch of actions into the console." ) + +MediaPlayer.DEBUG = MediaPlayer.Cvars.Debug:GetBool() + +cvars.AddChangeCallback( "mediaplayer_debug", function(name, old, new) + MediaPlayer.DEBUG = new == 1 +end) + +if SERVER then + AddCSLuaFile "cl_cvars.lua" +else + include "cl_cvars.lua" +end + + +--[[--------------------------------------------------------- + Config + + Store service API keys, etc. +-----------------------------------------------------------]] + +MediaPlayer.config = {} + +--- +-- Apply configuration values to the mediaplayer config. +-- +-- @param config Table with configuration values. +-- +function MediaPlayer.SetConfig( config ) + table.Merge( MediaPlayer.config, config ) +end + +--- +-- Method for easily grabbing config value without checking that each fragment +-- exists. +-- +-- @param key e.g. "json.key.fragments" +-- +function MediaPlayer.GetConfigValue( key ) + local value = table.Lookup( MediaPlayer.config, key ) + + if type(value) == 'nil' then + ErrorNoHalt("WARNING: MediaPlayer config value not found for key `" .. tostring(key) .. "`\n") + end + + return value +end + +if SERVER then + include "config/server.lua" +end + + +--[[--------------------------------------------------------- + Shared includes +-----------------------------------------------------------]] + +include "sh_mediaplayer.lua" +include "sh_services.lua" +include "sh_history.lua" + +hook.Add("Initialize", "InitMediaPlayer", function() + hook.Run("InitMediaPlayer", MediaPlayer) +end) + +-- No fun allowed +hook.Add( "CanDrive", "DisableMediaPlayerDriving", function(ply, ent) + if IsValid(ent) and ent.IsMediaPlayerEntity then + return IsValid(ply) and ply:IsAdmin() + end +end) diff --git a/lua/autorun/mediaplayer/sv_metadata.lua b/lua/autorun/mediaplayer/sv_metadata.lua new file mode 100644 index 0000000..336c9bc --- /dev/null +++ b/lua/autorun/mediaplayer/sv_metadata.lua @@ -0,0 +1,174 @@ +--[[--------------------------------------------------------- + Media Player Metadata + + All media metadata is cached in an SQLite table for quick + lookup and to prevent unnecessary network requests. +-----------------------------------------------------------]] + +MediaPlayer.Metadata = {} + +--- +-- Default metadata table name +-- @type String +-- +local TableName = "mediaplayer_metadata" + +--- +-- SQLite table struct +-- @type String +-- +local TableStruct = string.format([[ +CREATE TABLE %s ( + id VARCHAR(48) PRIMARY KEY, + title VARCHAR(128), + duration INTEGER NOT NULL DEFAULT 0, + thumbnail VARCHAR(512), + extra VARCHAR(2048), + request_count INTEGER NOT NULL DEFAULT 1, + last_request INTEGER NOT NULL DEFAULT 0, + last_updated INTEGER NOT NULL DEFAULT 0, + expired BOOLEAN NOT NULL DEFAULT 0 +)]], TableName) + +--- +-- Maximum cache age before it expires; currently one week in seconds. +-- @type Number +-- +local MaxCacheAge = 604800 + +--- +-- Query the metadata table for the given media object's metadata. +-- If the metadata is older than one week, it is ignored and replaced upon +-- saving. +-- +-- @param media Media service object. +-- @return table Cached metadata results. +-- +function MediaPlayer.Metadata:Query( media ) + local id = media:UniqueID() + if not id then return end + + local query = ("SELECT * FROM `%s` WHERE id='%s'"):format(TableName, id) + + if MediaPlayer.DEBUG then + print("MediaPlayer.Metadata.Query") + print(query) + end + + local results = sql.QueryRow(query) + + if results then + local expired = ( tonumber(results.expired) == 1 ) + + -- Media metadata has been marked as out-of-date + if expired then + return nil + end + + local lastupdated = tonumber( results.last_updated ) + local timediff = os.time() - lastupdated + + if timediff > MaxCacheAge then + + -- Set metadata entry as expired + query = "UPDATE `%s` SET expired=1 WHERE id='%s'" + query = query:format( TableName, id ) + + if MediaPlayer.DEBUG then + print("MediaPlayer.Metadata.Query: Setting entry as expired") + print(query) + end + + sql.Query( query ) + + return nil + + else + return results + end + elseif results == false then + ErrorNoHalt("MediaPlayer.Metadata.Query: There was an error executing the SQL query\n") + print(query) + end + + return nil +end + +--- +-- Save or update the given media object into the metadata table. +-- +-- @param media Media service object. +-- @return table SQL query results. +-- +function MediaPlayer.Metadata:Save( media ) + local id = media:UniqueID() + if not id then return end + + local query = ("SELECT expired FROM `%s` WHERE id='%s'"):format(TableName, id) + local results = sql.Query(query) + + if istable(results) then -- update + + if MediaPlayer.DEBUG then + print("MediaPlayer.Metadata.Save Results:") + PrintTable(results) + end + + results = results[1] + + local expired = ( tonumber(results.expired) == 1 ) + + if expired then + + -- Update possible new metadata + query = "UPDATE `%s` SET request_count=request_count+1, title=%s, duration=%s, thumbnail=%s, extra=%s, last_request=%s, last_updated=%s, expired=0 WHERE id='%s'" + query = query:format( TableName, + sql.SQLStr( media:Title() ), + media:Duration(), + sql.SQLStr( media:Thumbnail() ), + sql.SQLStr( util.TableToJSON(media._metadata.extra) ), + os.time(), + os.time(), + id ) + + else + + query = "UPDATE `%s` SET request_count=request_count+1, last_request=%s WHERE id='%s'" + query = query:format( TableName, os.time(), id ) + + end + + else -- insert + + query = string.format( "INSERT INTO `%s` ", TableName ) .. + "(id,title,duration,thumbnail,extra,last_request,last_updated) VALUES (" .. + string.format( "'%s',", id ) .. + string.format( "%s,", sql.SQLStr( media:Title() ) ) .. + string.format( "%s,", media:Duration() ) .. + string.format( "%s,", sql.SQLStr( media:Thumbnail() ) ) .. + string.format( "%s,", sql.SQLStr( util.TableToJSON(media._metadata.extra) ) ) .. + string.format( "%d,", os.time() ) .. + string.format( "%d)", os.time() ) + + end + + if MediaPlayer.DEBUG then + print("MediaPlayer.Metadata.Save") + print(query) + end + + results = sql.Query(query) + + if results == false then + ErrorNoHalt("MediaPlayer.Metadata.Save: There was an error executing the SQL query\n") + print(query) + end + + return results +end + +-- Create the SQLite table if it doesn't exist +if not sql.TableExists(TableName) then + Msg("MediaPlayer.Metadata: Creating `" .. TableName .. "` table...\n") + sql.Query(TableStruct) +end diff --git a/lua/autorun/menubar/mp_options.lua b/lua/autorun/menubar/mp_options.lua new file mode 100644 index 0000000..19fe072 --- /dev/null +++ b/lua/autorun/menubar/mp_options.lua @@ -0,0 +1,8 @@ + +hook.Add( "PopulateMenuBar", "MediaPlayerOptions_MenuBar", function( menubar ) + + local m = menubar:AddOrGetMenu( "Media Player" ) + + m:AddCVar( "Fullscreen", "mediaplayer_fullscreen", "1", "0" ) + +end ) diff --git a/lua/autorun/properties/mediaplayer.lua b/lua/autorun/properties/mediaplayer.lua new file mode 100644 index 0000000..d93ec19 --- /dev/null +++ b/lua/autorun/properties/mediaplayer.lua @@ -0,0 +1,185 @@ +AddCSLuaFile() + +local mporder = 3200 + +-- +-- Adds a media player property. +-- +-- Blue icons correspond to admin actions. +-- +local function AddMediaPlayerProperty( name, config ) + -- Assign incrementing order ID + config.Order = mporder + mporder = mporder + 1 + + properties.Add( name, config ) +end + +local function IsMediaPlayer( self, ent, ply ) + return IsValid(ent) and IsValid(ply) and + IsValid(ent:GetMediaPlayer()) and + gamemode.Call( "CanProperty", ply, self.InternalName, ent ) +end + +local function IsPrivilegedMediaPlayer( self, ent, ply ) + return IsMediaPlayer( self, ent, ply ) and + ( ply:IsAdmin() or ent:GetOwner() == ply ) +end + +local function HasMedia( mp ) + return mp:GetPlayerState() >= MP_STATE_PLAYING +end + +AddMediaPlayerProperty( "mp-pause", { + MenuLabel = "Pause", + MenuIcon = "icon16/control_pause_blue.png", + + Filter = function( self, ent, ply ) + if not IsPrivilegedMediaPlayer(self, ent, ply) then return end + local mp = ent:GetMediaPlayer() + return IsValid(mp) and mp:GetPlayerState() == MP_STATE_PLAYING + end, + + Action = function( self, ent ) + MediaPlayer.Pause( ent ) + end +}) + +AddMediaPlayerProperty( "mp-resume", { + MenuLabel = "Resume", + MenuIcon = "icon16/control_play_blue.png", + + Filter = function( self, ent, ply ) + if not IsPrivilegedMediaPlayer(self, ent, ply) then return end + local mp = ent:GetMediaPlayer() + return IsValid(mp) and mp:GetPlayerState() == MP_STATE_PAUSED + end, + + Action = function( self, ent ) + MediaPlayer.Pause( ent ) + end +}) + +AddMediaPlayerProperty( "mp-skip", { + MenuLabel = "Skip", + MenuIcon = "icon16/control_end_blue.png", + + Filter = function( self, ent, ply ) + if not IsPrivilegedMediaPlayer(self, ent, ply) then return end + local mp = ent:GetMediaPlayer() + return IsValid(mp) and HasMedia(mp) + end, + + Action = function( self, ent ) + MediaPlayer.Skip( ent ) + end +}) + +AddMediaPlayerProperty( "mp-seek", { + MenuLabel = "Seek", + -- MenuIcon = "icon16/timeline_marker.png", + MenuIcon = "icon16/control_fastforward_blue.png", + + Filter = function( self, ent, ply ) + if not IsPrivilegedMediaPlayer(self, ent, ply) then return end + local mp = ent:GetMediaPlayer() + return IsValid(mp) and HasMedia(mp) + end, + + Action = function( self, ent ) + + Derma_StringRequest( + "Media Player", + "Enter a time in HH:MM:SS format (hours, minutes, seconds):", + "", -- Default text + function( time ) + MediaPlayer.Seek( ent, time ) + end, + function() end, + "Seek", + "Cancel" + ) + + end +}) + +AddMediaPlayerProperty( "mp-request-url", { + MenuLabel = "Request URL", + MenuIcon = "icon16/link_add.png", + Filter = IsMediaPlayer, + + Action = function( self, ent ) + + --[[ Old request dialog + Derma_StringRequest( + "Media Player", -- Title + "Enter a URL to request:", -- Subtitle + "", -- Default text + function( url ) + MediaPlayer.Request( ent, url ) + end, + function() end, + "Request", + "Cancel" + ) + ]] + + MediaPlayer.OpenRequestMenu( ent ) + + end +}) + +AddMediaPlayerProperty( "mp-copy-url", { + MenuLabel = "Copy URL to clipboard", + -- MenuIcon = "icon16/link.png", + MenuIcon = "icon16/paste_plain.png", + + Filter = function( self, ent, ply ) + if not IsMediaPlayer(self, ent, ply) then return end + local mp = ent:GetMediaPlayer() + return IsValid(mp) and HasMedia(mp) + end, + + Action = function( self, ent ) + + local mp = ent:GetMediaPlayer() + local media = mp and mp:CurrentMedia() + if not IsValid(media) then return end + + SetClipboardText( media:Url() ) + LocalPlayer():ChatPrint( "Media URL has been copied into your clipboard." ) + + end +}) + +AddMediaPlayerProperty( "mp-enable", { + MenuLabel = "Turn On", + MenuIcon = "icon16/lightbulb.png", + + Filter = function( self, ent, ply ) + return IsValid(ent) and IsValid(ply) and + ent.IsMediaPlayerEntity and + not IsValid(ent:GetMediaPlayer()) and + gamemode.Call( "CanProperty", ply, self.InternalName, ent ) + end, + + Action = function( self, ent ) + MediaPlayer.RequestListen( ent ) + end +}) + +AddMediaPlayerProperty( "mp-disable", { + MenuLabel = "Turn Off", + MenuIcon = "icon16/lightbulb_off.png", + + Filter = function( self, ent, ply ) + return IsValid(ent) and IsValid(ply) and + ent.IsMediaPlayerEntity and + IsValid(ent:GetMediaPlayer()) and + gamemode.Call( "CanProperty", ply, self.InternalName, ent ) + end, + + Action = function( self, ent ) + MediaPlayer.RequestListen( ent ) + end +}) diff --git a/lua/entities/mediaplayer_base/shared.lua b/lua/entities/mediaplayer_base/shared.lua new file mode 100644 index 0000000..dc640c4 --- /dev/null +++ b/lua/entities/mediaplayer_base/shared.lua @@ -0,0 +1,84 @@ +AddCSLuaFile() + +ENT.Type = "anim" +ENT.Base = "base_anim" + +ENT.Spawnable = false + +ENT.Model = Model( "models/gmod_tower/suitetv.mdl" ) + +ENT.MediaPlayerType = "entity" +ENT.UseDelay = 0.5 -- seconds + +ENT.IsMediaPlayerEntity = true + +function ENT:Initialize() + + if SERVER then + self:SetModel( self.Model ) + + self:SetUseType( SIMPLE_USE ) + + self:PhysicsInit( SOLID_VPHYSICS ) + self:SetMoveType( MOVETYPE_VPHYSICS ) + + local phys = self:GetPhysicsObject() + if IsValid( phys ) then + phys:EnableMotion( false ) + end + + self:DrawShadow( false ) + + -- Install media player to entity + self:InstallMediaPlayer( self.MediaPlayerType ) + + -- Network media player ID + local mp = self:GetMediaPlayer() + self:SetMediaPlayerID( mp:GetId() ) + end + +end + +function ENT:SetupDataTables() + + self:NetworkVar( "String", 0, "MediaPlayerID" ) + +end + +function ENT:Use(ply) + + if not IsValid(ply) then return end + + -- Delay request + if ply.NextUse and ply.NextUse > CurTime() then + return + end + + local mp = self:GetMediaPlayer() + + if not mp then + ErrorNoHalt("MediaPlayer test entity doesn't have player installed\n") + debug.Trace() + return + end + + if mp:HasListener(ply) then + mp:RemoveListener(ply) + else + mp:AddListener(ply) + end + + ply.NextUse = CurTime() + self.UseDelay + +end + +function ENT:OnRemove() + local mp = self:GetMediaPlayer() + if mp then + mp:Remove() + end +end + +function ENT:UpdateTransmitState() + return TRANSMIT_PVS +end diff --git a/lua/entities/mediaplayer_example/shared.lua b/lua/entities/mediaplayer_example/shared.lua new file mode 100644 index 0000000..760fa16 --- /dev/null +++ b/lua/entities/mediaplayer_example/shared.lua @@ -0,0 +1,20 @@ +AddCSLuaFile() + +ENT.PrintName = "Media Player Example" +ENT.Author = "Samuel Maddock" +ENT.Instructions = "Right click on the TV to see available Media Player options. Alternatively, press E on the TV to turn it on." +ENT.Category = "Media Player" + +ENT.Type = "anim" +ENT.Base = "mediaplayer_base" + +ENT.Spawnable = true + +ENT.Model = Model( "models/gmod_tower/suitetv_large.mdl" ) + +ENT.PlayerConfig = { + angle = Angle(-90, 90, 0), + offset = Vector(6, 59.49, 103.65), + width = 119, + height = 69 +} diff --git a/lua/entities/mediaplayer_projector/shared.lua b/lua/entities/mediaplayer_projector/shared.lua new file mode 100644 index 0000000..8490739 --- /dev/null +++ b/lua/entities/mediaplayer_projector/shared.lua @@ -0,0 +1,86 @@ +AddCSLuaFile() +DEFINE_BASECLASS( "mediaplayer_base" ) + +ENT.PrintName = "Media Player Projector" +ENT.Author = "Samuel Maddock" +ENT.Instructions = "Press Use on the entity to start watching." +ENT.Category = "Media Player" + +ENT.Type = "anim" +ENT.Base = "mediaplayer_base" +ENT.RenderGroup = RENDERGROUP_BOTH + +ENT.Spawnable = true + +ENT.Model = Model( "models/props/cs_office/projector.mdl" ) + +function ENT:SetupDataTables() + + self:NetworkVar( "Entity", 0, "Flashlight" ) + +end + +-- TODO: figure out how to get this to work; will probably involve using a +-- render target + +if SERVER then + + function ENT:Initialize() + + BaseClass.Initialize(self) + + self.flashlight = ents.Create( "env_projectedtexture" ) + + self.flashlight:SetParent( self.Entity ) + + -- The local positions are the offsets from parent.. + self.flashlight:SetLocalPos( Vector( 0, 0, 0 ) ) + self.flashlight:SetLocalAngles( Angle(0,0,0) ) + + -- Looks like only one flashlight can have shadows enabled! + self.flashlight:SetKeyValue( "enableshadows", 1 ) + self.flashlight:SetKeyValue( "farz", 1024 ) + self.flashlight:SetKeyValue( "nearz", 12 ) + self.flashlight:SetKeyValue( "lightfov", 90 ) + + local c = self:GetColor() + local b = 1 + self.flashlight:SetKeyValue( "lightcolor", Format( "%i %i %i 255", c.r * b, c.g * b, c.b * b ) ) + + self.flashlight:Spawn() + + self.flashlight:Input( "SpotlightTexture", NULL, NULL, "vgui/hand.vtf" ) + + self:SetFlashlight(self.flashlight) + + end + +else + + local projmat = CreateMaterial( "projmat", "UnlitGeneric", { + ["$basetexture"] = "vgui/hand.vtf" + }) + + function ENT:Draw() + + BaseClass.Draw(self) + + local flashlight = self:GetFlashlight() + if not IsValid(flashlight) then return end + + local mp = self:GetMediaPlayer() + if not IsValid(mp) then return end + + local media = mp:CurrentMedia() + local browser = media and media.Browser or MediaPlayer.GetIdlescreen() + + browser:UpdateHTMLTexture() + local mat = browser:GetHTMLMaterial() + + local texture = mat:GetTexture("$basetexture") + + projmat:SetTexture( "$basetexture", texture ) + + end + +end diff --git a/mediaplayer.sublime-project b/mediaplayer.sublime-project new file mode 100644 index 0000000..24db303 --- /dev/null +++ b/mediaplayer.sublime-project @@ -0,0 +1,8 @@ +{ + "folders": + [ + { + "path": "." + } + ] +} diff --git a/resource/fonts/ClearSans-Medium.ttf b/resource/fonts/ClearSans-Medium.ttf new file mode 100644 index 0000000000000000000000000000000000000000..421a81026a6f4e36b82aaf7bf328945b7adbe671 GIT binary patch literal 70736 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 z03J58�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 IM`307KMLl?