├── .gitignore ├── AUTHORS ├── COPYING ├── ChangeLog ├── INSTALL ├── Makefile.am ├── NEWS ├── README ├── TODO ├── android ├── .gitignore ├── AndroidManifest.xml ├── Makefile ├── jni │ ├── Android.mk │ └── android-aurena.c ├── res │ ├── drawable-ldpi │ │ └── aurena.png │ ├── drawable-xhdpi │ │ └── aurena.png │ ├── layout │ │ └── main.xml │ └── values │ │ └── strings.xml └── src │ └── net │ └── noraisin │ └── android_aurena │ └── AndroidAurena.java ├── autogen.sh ├── configure.ac ├── content └── config ├── data ├── Makefile.am ├── htdocs │ ├── Makefile.am │ ├── css │ │ ├── aurena.css │ │ └── smoothness │ │ │ ├── images │ │ │ ├── ui-bg_flat_0_aaaaaa_40x100.png │ │ │ ├── ui-bg_flat_75_ffffff_40x100.png │ │ │ ├── ui-bg_glass_55_fbf9ee_1x400.png │ │ │ ├── ui-bg_glass_65_ffffff_1x400.png │ │ │ ├── ui-bg_glass_75_dadada_1x400.png │ │ │ ├── ui-bg_glass_75_e6e6e6_1x400.png │ │ │ ├── ui-bg_glass_95_fef1ec_1x400.png │ │ │ ├── ui-bg_highlight-soft_75_cccccc_1x100.png │ │ │ ├── ui-icons_222222_256x240.png │ │ │ ├── ui-icons_2e83ff_256x240.png │ │ │ ├── ui-icons_454545_256x240.png │ │ │ ├── ui-icons_888888_256x240.png │ │ │ └── ui-icons_cd0a0a_256x240.png │ │ │ └── jquery-ui-1.8.22.custom.css │ ├── icon.png │ ├── index.html │ └── js │ │ ├── aurena.js │ │ ├── jquery-1.7.2.min.js │ │ └── jquery-ui-1.8.22.custom.min.js ├── logo.svg ├── simple-controller-client.ui └── simple-controller.ui ├── src ├── Makefile.am ├── aurena-server.c ├── client │ ├── Makefile.am │ ├── aur-client.c │ └── aur-client.h ├── common │ ├── aur-json.c │ ├── aur-json.h │ ├── aur-types.h │ ├── aur-websocket-parser.c │ └── aur-websocket-parser.h ├── gnome-client.py ├── icon.xcf ├── server │ ├── Makefile.am │ ├── aur-avahi.c │ ├── aur-avahi.h │ ├── aur-config.c │ ├── aur-config.h │ ├── aur-http-resource.c │ ├── aur-http-resource.h │ ├── aur-manager.c │ ├── aur-manager.h │ ├── aur-media-db.c │ ├── aur-media-db.h │ ├── aur-resource.c │ ├── aur-resource.h │ ├── aur-server-client.c │ ├── aur-server-client.h │ ├── aur-server.c │ └── aur-server.h ├── simple-client-fullscreen.c ├── simple-client.c └── simple-controller.c └── test ├── .gitignore ├── Makefile.am ├── clock-bouncer.c ├── clock-receiver.c ├── clock-server.c └── play-test.py /.gitignore: -------------------------------------------------------------------------------- 1 | aclocal.m4 2 | autom4te.cache/ 3 | aux/ 4 | config.guess 5 | config.h 6 | config.h.in 7 | config.log 8 | config.status 9 | config.sub 10 | configure 11 | content/ 12 | depcomp 13 | install-sh 14 | libtool 15 | ltmain.sh 16 | Makefile 17 | !android/Makefile 18 | Makefile.in 19 | missing 20 | src/aurena-server 21 | src/aurena-simple-client 22 | src/aurena-simple-client-fullscreen 23 | src/aurena-simple-controller 24 | stamp-h1 25 | .deps 26 | *.swp 27 | *.la 28 | *.o 29 | *.lo 30 | *.bz2 31 | *.xz 32 | .libs/ 33 | *.pyc 34 | *~ 35 | -------------------------------------------------------------------------------- /AUTHORS: -------------------------------------------------------------------------------- 1 | Jan Schmidt 2 | -------------------------------------------------------------------------------- /ChangeLog: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thaytan/aurena/ccc0530a57edf8e86729abaf378779d2e689aabf/ChangeLog -------------------------------------------------------------------------------- /INSTALL: -------------------------------------------------------------------------------- 1 | Installation Instructions 2 | ************************* 3 | 4 | Copyright (C) 1994-1996, 1999-2002, 2004-2011 Free Software Foundation, 5 | Inc. 6 | 7 | Copying and distribution of this file, with or without modification, 8 | are permitted in any medium without royalty provided the copyright 9 | notice and this notice are preserved. This file is offered as-is, 10 | without warranty of any kind. 11 | 12 | Basic Installation 13 | ================== 14 | 15 | Briefly, the shell commands `./configure; make; make install' should 16 | configure, build, and install this package. The following 17 | more-detailed instructions are generic; see the `README' file for 18 | instructions specific to this package. Some packages provide this 19 | `INSTALL' file but do not implement all of the features documented 20 | below. The lack of an optional feature in a given package is not 21 | necessarily a bug. More recommendations for GNU packages can be found 22 | in *note Makefile Conventions: (standards)Makefile Conventions. 23 | 24 | The `configure' shell script attempts to guess correct values for 25 | various system-dependent variables used during compilation. It uses 26 | those values to create a `Makefile' in each directory of the package. 27 | It may also create one or more `.h' files containing system-dependent 28 | definitions. Finally, it creates a shell script `config.status' that 29 | you can run in the future to recreate the current configuration, and a 30 | file `config.log' containing compiler output (useful mainly for 31 | debugging `configure'). 32 | 33 | It can also use an optional file (typically called `config.cache' 34 | and enabled with `--cache-file=config.cache' or simply `-C') that saves 35 | the results of its tests to speed up reconfiguring. Caching is 36 | disabled by default to prevent problems with accidental use of stale 37 | cache files. 38 | 39 | If you need to do unusual things to compile the package, please try 40 | to figure out how `configure' could check whether to do them, and mail 41 | diffs or instructions to the address given in the `README' so they can 42 | be considered for the next release. If you are using the cache, and at 43 | some point `config.cache' contains results you don't want to keep, you 44 | may remove or edit it. 45 | 46 | The file `configure.ac' (or `configure.in') is used to create 47 | `configure' by a program called `autoconf'. You need `configure.ac' if 48 | you want to change it or regenerate `configure' using a newer version 49 | of `autoconf'. 50 | 51 | The simplest way to compile this package is: 52 | 53 | 1. `cd' to the directory containing the package's source code and type 54 | `./configure' to configure the package for your system. 55 | 56 | Running `configure' might take a while. While running, it prints 57 | some messages telling which features it is checking for. 58 | 59 | 2. Type `make' to compile the package. 60 | 61 | 3. Optionally, type `make check' to run any self-tests that come with 62 | the package, generally using the just-built uninstalled binaries. 63 | 64 | 4. Type `make install' to install the programs and any data files and 65 | documentation. When installing into a prefix owned by root, it is 66 | recommended that the package be configured and built as a regular 67 | user, and only the `make install' phase executed with root 68 | privileges. 69 | 70 | 5. Optionally, type `make installcheck' to repeat any self-tests, but 71 | this time using the binaries in their final installed location. 72 | This target does not install anything. Running this target as a 73 | regular user, particularly if the prior `make install' required 74 | root privileges, verifies that the installation completed 75 | correctly. 76 | 77 | 6. You can remove the program binaries and object files from the 78 | source code directory by typing `make clean'. To also remove the 79 | files that `configure' created (so you can compile the package for 80 | a different kind of computer), type `make distclean'. There is 81 | also a `make maintainer-clean' target, but that is intended mainly 82 | for the package's developers. If you use it, you may have to get 83 | all sorts of other programs in order to regenerate files that came 84 | with the distribution. 85 | 86 | 7. Often, you can also type `make uninstall' to remove the installed 87 | files again. In practice, not all packages have tested that 88 | uninstallation works correctly, even though it is required by the 89 | GNU Coding Standards. 90 | 91 | 8. Some packages, particularly those that use Automake, provide `make 92 | distcheck', which can by used by developers to test that all other 93 | targets like `make install' and `make uninstall' work correctly. 94 | This target is generally not run by end users. 95 | 96 | Compilers and Options 97 | ===================== 98 | 99 | Some systems require unusual options for compilation or linking that 100 | the `configure' script does not know about. Run `./configure --help' 101 | for details on some of the pertinent environment variables. 102 | 103 | You can give `configure' initial values for configuration parameters 104 | by setting variables in the command line or in the environment. Here 105 | is an example: 106 | 107 | ./configure CC=c99 CFLAGS=-g LIBS=-lposix 108 | 109 | *Note Defining Variables::, for more details. 110 | 111 | Compiling For Multiple Architectures 112 | ==================================== 113 | 114 | You can compile the package for more than one kind of computer at the 115 | same time, by placing the object files for each architecture in their 116 | own directory. To do this, you can use GNU `make'. `cd' to the 117 | directory where you want the object files and executables to go and run 118 | the `configure' script. `configure' automatically checks for the 119 | source code in the directory that `configure' is in and in `..'. This 120 | is known as a "VPATH" build. 121 | 122 | With a non-GNU `make', it is safer to compile the package for one 123 | architecture at a time in the source code directory. After you have 124 | installed the package for one architecture, use `make distclean' before 125 | reconfiguring for another architecture. 126 | 127 | On MacOS X 10.5 and later systems, you can create libraries and 128 | executables that work on multiple system types--known as "fat" or 129 | "universal" binaries--by specifying multiple `-arch' options to the 130 | compiler but only a single `-arch' option to the preprocessor. Like 131 | this: 132 | 133 | ./configure CC="gcc -arch i386 -arch x86_64 -arch ppc -arch ppc64" \ 134 | CXX="g++ -arch i386 -arch x86_64 -arch ppc -arch ppc64" \ 135 | CPP="gcc -E" CXXCPP="g++ -E" 136 | 137 | This is not guaranteed to produce working output in all cases, you 138 | may have to build one architecture at a time and combine the results 139 | using the `lipo' tool if you have problems. 140 | 141 | Installation Names 142 | ================== 143 | 144 | By default, `make install' installs the package's commands under 145 | `/usr/local/bin', include files under `/usr/local/include', etc. You 146 | can specify an installation prefix other than `/usr/local' by giving 147 | `configure' the option `--prefix=PREFIX', where PREFIX must be an 148 | absolute file name. 149 | 150 | You can specify separate installation prefixes for 151 | architecture-specific files and architecture-independent files. If you 152 | pass the option `--exec-prefix=PREFIX' to `configure', the package uses 153 | PREFIX as the prefix for installing programs and libraries. 154 | Documentation and other data files still use the regular prefix. 155 | 156 | In addition, if you use an unusual directory layout you can give 157 | options like `--bindir=DIR' to specify different values for particular 158 | kinds of files. Run `configure --help' for a list of the directories 159 | you can set and what kinds of files go in them. In general, the 160 | default for these options is expressed in terms of `${prefix}', so that 161 | specifying just `--prefix' will affect all of the other directory 162 | specifications that were not explicitly provided. 163 | 164 | The most portable way to affect installation locations is to pass the 165 | correct locations to `configure'; however, many packages provide one or 166 | both of the following shortcuts of passing variable assignments to the 167 | `make install' command line to change installation locations without 168 | having to reconfigure or recompile. 169 | 170 | The first method involves providing an override variable for each 171 | affected directory. For example, `make install 172 | prefix=/alternate/directory' will choose an alternate location for all 173 | directory configuration variables that were expressed in terms of 174 | `${prefix}'. Any directories that were specified during `configure', 175 | but not in terms of `${prefix}', must each be overridden at install 176 | time for the entire installation to be relocated. The approach of 177 | makefile variable overrides for each directory variable is required by 178 | the GNU Coding Standards, and ideally causes no recompilation. 179 | However, some platforms have known limitations with the semantics of 180 | shared libraries that end up requiring recompilation when using this 181 | method, particularly noticeable in packages that use GNU Libtool. 182 | 183 | The second method involves providing the `DESTDIR' variable. For 184 | example, `make install DESTDIR=/alternate/directory' will prepend 185 | `/alternate/directory' before all installation names. The approach of 186 | `DESTDIR' overrides is not required by the GNU Coding Standards, and 187 | does not work on platforms that have drive letters. On the other hand, 188 | it does better at avoiding recompilation issues, and works well even 189 | when some directory options were not specified in terms of `${prefix}' 190 | at `configure' time. 191 | 192 | Optional Features 193 | ================= 194 | 195 | If the package supports it, you can cause programs to be installed 196 | with an extra prefix or suffix on their names by giving `configure' the 197 | option `--program-prefix=PREFIX' or `--program-suffix=SUFFIX'. 198 | 199 | Some packages pay attention to `--enable-FEATURE' options to 200 | `configure', where FEATURE indicates an optional part of the package. 201 | They may also pay attention to `--with-PACKAGE' options, where PACKAGE 202 | is something like `gnu-as' or `x' (for the X Window System). The 203 | `README' should mention any `--enable-' and `--with-' options that the 204 | package recognizes. 205 | 206 | For packages that use the X Window System, `configure' can usually 207 | find the X include and library files automatically, but if it doesn't, 208 | you can use the `configure' options `--x-includes=DIR' and 209 | `--x-libraries=DIR' to specify their locations. 210 | 211 | Some packages offer the ability to configure how verbose the 212 | execution of `make' will be. For these packages, running `./configure 213 | --enable-silent-rules' sets the default to minimal output, which can be 214 | overridden with `make V=1'; while running `./configure 215 | --disable-silent-rules' sets the default to verbose, which can be 216 | overridden with `make V=0'. 217 | 218 | Particular systems 219 | ================== 220 | 221 | On HP-UX, the default C compiler is not ANSI C compatible. If GNU 222 | CC is not installed, it is recommended to use the following options in 223 | order to use an ANSI C compiler: 224 | 225 | ./configure CC="cc -Ae -D_XOPEN_SOURCE=500" 226 | 227 | and if that doesn't work, install pre-built binaries of GCC for HP-UX. 228 | 229 | HP-UX `make' updates targets which have the same time stamps as 230 | their prerequisites, which makes it generally unusable when shipped 231 | generated files such as `configure' are involved. Use GNU `make' 232 | instead. 233 | 234 | On OSF/1 a.k.a. Tru64, some versions of the default C compiler cannot 235 | parse its `' header file. The option `-nodtk' can be used as 236 | a workaround. If GNU CC is not installed, it is therefore recommended 237 | to try 238 | 239 | ./configure CC="cc" 240 | 241 | and if that doesn't work, try 242 | 243 | ./configure CC="cc -nodtk" 244 | 245 | On Solaris, don't put `/usr/ucb' early in your `PATH'. This 246 | directory contains several dysfunctional programs; working variants of 247 | these programs are available in `/usr/bin'. So, if you need `/usr/ucb' 248 | in your `PATH', put it _after_ `/usr/bin'. 249 | 250 | On Haiku, software installed for all users goes in `/boot/common', 251 | not `/usr/local'. It is recommended to use the following options: 252 | 253 | ./configure --prefix=/boot/common 254 | 255 | Specifying the System Type 256 | ========================== 257 | 258 | There may be some features `configure' cannot figure out 259 | automatically, but needs to determine by the type of machine the package 260 | will run on. Usually, assuming the package is built to be run on the 261 | _same_ architectures, `configure' can figure that out, but if it prints 262 | a message saying it cannot guess the machine type, give it the 263 | `--build=TYPE' option. TYPE can either be a short name for the system 264 | type, such as `sun4', or a canonical name which has the form: 265 | 266 | CPU-COMPANY-SYSTEM 267 | 268 | where SYSTEM can have one of these forms: 269 | 270 | OS 271 | KERNEL-OS 272 | 273 | See the file `config.sub' for the possible values of each field. If 274 | `config.sub' isn't included in this package, then this package doesn't 275 | need to know the machine type. 276 | 277 | If you are _building_ compiler tools for cross-compiling, you should 278 | use the option `--target=TYPE' to select the type of system they will 279 | produce code for. 280 | 281 | If you want to _use_ a cross compiler, that generates code for a 282 | platform different from the build platform, you should specify the 283 | "host" platform (i.e., that on which the generated programs will 284 | eventually be run) with `--host=TYPE'. 285 | 286 | Sharing Defaults 287 | ================ 288 | 289 | If you want to set default values for `configure' scripts to share, 290 | you can create a site shell script called `config.site' that gives 291 | default values for variables like `CC', `cache_file', and `prefix'. 292 | `configure' looks for `PREFIX/share/config.site' if it exists, then 293 | `PREFIX/etc/config.site' if it exists. Or, you can set the 294 | `CONFIG_SITE' environment variable to the location of the site script. 295 | A warning: not all `configure' scripts look for a site script. 296 | 297 | Defining Variables 298 | ================== 299 | 300 | Variables not defined in a site shell script can be set in the 301 | environment passed to `configure'. However, some packages may run 302 | configure again during the build, and the customized values of these 303 | variables may be lost. In order to avoid this problem, you should set 304 | them in the `configure' command line, using `VAR=value'. For example: 305 | 306 | ./configure CC=/usr/local2/bin/gcc 307 | 308 | causes the specified `gcc' to be used as the C compiler (unless it is 309 | overridden in the site shell script). 310 | 311 | Unfortunately, this technique does not work for `CONFIG_SHELL' due to 312 | an Autoconf bug. Until the bug is fixed you can use this workaround: 313 | 314 | CONFIG_SHELL=/bin/bash /bin/bash ./configure CONFIG_SHELL=/bin/bash 315 | 316 | `configure' Invocation 317 | ====================== 318 | 319 | `configure' recognizes the following options to control how it 320 | operates. 321 | 322 | `--help' 323 | `-h' 324 | Print a summary of all of the options to `configure', and exit. 325 | 326 | `--help=short' 327 | `--help=recursive' 328 | Print a summary of the options unique to this package's 329 | `configure', and exit. The `short' variant lists options used 330 | only in the top level, while the `recursive' variant lists options 331 | also present in any nested packages. 332 | 333 | `--version' 334 | `-V' 335 | Print the version of Autoconf used to generate the `configure' 336 | script, and exit. 337 | 338 | `--cache-file=FILE' 339 | Enable the cache: use and save the results of the tests in FILE, 340 | traditionally `config.cache'. FILE defaults to `/dev/null' to 341 | disable caching. 342 | 343 | `--config-cache' 344 | `-C' 345 | Alias for `--cache-file=config.cache'. 346 | 347 | `--quiet' 348 | `--silent' 349 | `-q' 350 | Do not print messages saying which checks are being made. To 351 | suppress all normal output, redirect it to `/dev/null' (any error 352 | messages will still be shown). 353 | 354 | `--srcdir=DIR' 355 | Look for the package's source code in directory DIR. Usually 356 | `configure' can determine that directory automatically. 357 | 358 | `--prefix=DIR' 359 | Use DIR as the installation prefix. *note Installation Names:: 360 | for more details, including other options available for fine-tuning 361 | the installation locations. 362 | 363 | `--no-create' 364 | `-n' 365 | Run the configure checks, but stop before creating any output 366 | files. 367 | 368 | `configure' also accepts some other, not widely useful, options. Run 369 | `configure --help' for more details. 370 | 371 | -------------------------------------------------------------------------------- /Makefile.am: -------------------------------------------------------------------------------- 1 | SUBDIRS = src data test 2 | EXTRA_DIST = content 3 | -------------------------------------------------------------------------------- /NEWS: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thaytan/aurena/ccc0530a57edf8e86729abaf378779d2e689aabf/NEWS -------------------------------------------------------------------------------- /README: -------------------------------------------------------------------------------- 1 | Aurena is a network distributed media playback system. It provides 2 | a server for media content, and clients for synchronised playback across 3 | all receivers, similar to some (expensive) commercial systems. 4 | 5 | To use this, just build it in the usual autoconf-ey way (configure && make) 6 | 7 | Building the clients requires GLib/GObject > 2.30, avahi-client and 8 | avahi-glib > 0.6.24, json-glib-1.0 and libsoup >= 2.26.1, as well as 9 | GStreamer 1.0 and gst-plugins-base libs. The GUI clients also require GTK+ 3.0 10 | 11 | To build the server binary, you additionally need sqlite >= 3.3, and 12 | a slightly newer GLib (>=2.32) and libsoup (> 2.48). You only need the server 13 | on one machine that will host the media files. Only a client app is needed 14 | on the receivers. 15 | 16 | After building, run src/aurena-server on one machine, and 17 | 'src/aurena-simple-client' (or one of the other client apps in src/) on 18 | several clients that can access the server machine 'server-host'. The clients 19 | should auto-detect the server. If that doesn't work (if Avahi broadasts are 20 | broken) pass the server-host name explicitly as a parameter to 21 | aurena-simple-client. 22 | 23 | For control, hit http://server-host:5457/ 24 | 25 | Notes/future ideas: 26 | 27 | *) media library with metadata - see available songs 28 | *) Multiple zones, ability to move players between zones 29 | **) pause zone when 0 players online 30 | 31 | - daemon portion - http + rtsp + network clock source 32 | + avahi announcements 33 | + base time distribution 34 | + http = libsoup 35 | + network clock source = gst 36 | - rtsp = gst-rtsp-server 37 | - player 38 | + playbin wrapper that does rtsp or http + network clock subscription 39 | and follows remote volume control, pause/play etc. 40 | 41 | 42 | Security 43 | ======== 44 | 45 | Very little effort has been put into making the server secure. It allows 46 | making clients play any arbitrary http URI. No authentication is required 47 | for clients to connect. 48 | 49 | This software should only ever be run on a private network, where all software 50 | running on the network and its clients is trusted. 51 | -------------------------------------------------------------------------------- /TODO: -------------------------------------------------------------------------------- 1 | Server TODO 2 | * Track metadata discovery 3 | * Support seeking. 4 | * Provide library hooks 5 | * Tighten up security 6 | 7 | Control UI 8 | * Song library, searching, enqueueing 9 | 10 | sqlite DB schema: 11 | 12 | table: paths 13 | id INTEGER PRIMARY KEY 14 | base_path TEXT 15 | 16 | table: files 17 | id INTEGER PRIMARY KEY 18 | base_path_id INTEGER 19 | filename TEXT 20 | timestamp TEXT 21 | duration INTEGER (nanoseconds) 22 | is_video INTEGER (0 or 1) 23 | 24 | table: songs 25 | id INTEGER PRIMARY KEY 26 | media_id INTEGER 27 | title TEXT 28 | artist TEXT 29 | 30 | -------------------------------------------------------------------------------- /android/.gitignore: -------------------------------------------------------------------------------- 1 | assets/ 2 | bin/ 3 | build.xml 4 | gen/ 5 | gst-build-armeabi/ 6 | gst-build/ 7 | libs/ 8 | local.properties 9 | obj/ 10 | proguard-project.txt 11 | project.properties 12 | src/org/ 13 | -------------------------------------------------------------------------------- /android/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 8 | 9 | 10 | 13 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /android/Makefile: -------------------------------------------------------------------------------- 1 | GSTREAMER_SDK_ROOT_ANDROID ?= $(HOME)/cerbero/dist/android_armv7 2 | 3 | build: setup 4 | GSTREAMER_SDK_ROOT_ANDROID=$(GSTREAMER_SDK_ROOT_ANDROID) ndk-build NDK_DEBUG=1 5 | ant debug 6 | 7 | setup: build.xml 8 | 9 | build.xml: AndroidManifest.xml 10 | android update project -p . -s --target android-16 11 | touch -c build.xml # project update won't change timestamp if no change 12 | 13 | install: 14 | adb install -r bin/AndroidAurena-debug.apk 15 | 16 | clean: 17 | rm -f setup.stamp 18 | -------------------------------------------------------------------------------- /android/jni/Android.mk: -------------------------------------------------------------------------------- 1 | LOCAL_PATH := $(call my-dir) 2 | APP_PLATFORM := android-16 3 | 4 | include $(CLEAR_VARS) 5 | 6 | 7 | ifndef GSTREAMER_ROOT 8 | ifndef GSTREAMER_SDK_ROOT_ANDROID 9 | $(error GSTREAMER_SDK_ROOT_ANDROID is not defined!) 10 | endif 11 | GSTREAMER_ROOT := $(GSTREAMER_SDK_ROOT_ANDROID) 12 | endif 13 | 14 | LOCAL_MODULE := android-aurena 15 | LOCAL_SRC_FILES := android-aurena.c ../../src/common/aur-json.c ../../src/client/aur-client.c 16 | LOCAL_SHARED_LIBRARIES := gstreamer_android 17 | LOCAL_C_INCLUDES := $(LOCAL_PATH)/../.. 18 | LOCAL_LDLIBS := -landroid 19 | include $(BUILD_SHARED_LIBRARY) 20 | 21 | GSTREAMER_NDK_BUILD_PATH := $(GSTREAMER_ROOT)/share/gst-android/ndk-build/ 22 | include $(GSTREAMER_NDK_BUILD_PATH)/plugins.mk 23 | GSTREAMER_PLUGINS := $(GSTREAMER_PLUGINS_CORE) $(GSTREAMER_PLUGINS_PLAYBACK) $(GSTREAMER_PLUGINS_CODECS) $(GSTREAMER_PLUGINS_NET) $(GSTREAMER_PLUGINS_SYS) 24 | G_IO_MODULES := gnutls 25 | GSTREAMER_EXTRA_DEPS := glib-2.0 json-glib-1.0 libsoup-2.4 gstreamer-net-1.0 26 | include $(GSTREAMER_NDK_BUILD_PATH)/gstreamer-1.0.mk 27 | -------------------------------------------------------------------------------- /android/jni/android-aurena.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | 10 | #include "src/client/aur-client.h" 11 | 12 | GST_DEBUG_CATEGORY_STATIC (debug_category); 13 | #define GST_CAT_DEFAULT debug_category 14 | 15 | /* 16 | * These macros provide a way to store the native pointer to CustomData, which might be 32 or 64 bits, into 17 | * a jlong, which is always 64 bits, without warnings. 18 | */ 19 | #if GLIB_SIZEOF_VOID_P == 8 20 | # define GET_CUSTOM_DATA(env, thiz, fieldID) (CustomData *)(*env)->GetLongField (env, thiz, fieldID) 21 | # define SET_CUSTOM_DATA(env, thiz, fieldID, data) (*env)->SetLongField (env, thiz, fieldID, (jlong)data) 22 | #else 23 | # define GET_CUSTOM_DATA(env, thiz, fieldID) (CustomData *)(jint)(*env)->GetLongField (env, thiz, fieldID) 24 | # define SET_CUSTOM_DATA(env, thiz, fieldID, data) (*env)->SetLongField (env, thiz, fieldID, (jlong)(jint)data) 25 | #endif 26 | 27 | typedef struct _CustomData 28 | { 29 | jobject app; 30 | GMainContext *context; 31 | GMainLoop *main_loop; 32 | ANativeWindow *native_window; 33 | AurClient *client; 34 | gint64 position; 35 | gint64 duration; 36 | gboolean initialized; 37 | gchar *server; 38 | } CustomData; 39 | 40 | static pthread_t gst_app_thread; 41 | static pthread_key_t current_jni_env; 42 | static JavaVM *java_vm; 43 | static jfieldID custom_data_field_id; 44 | static jmethodID set_message_method_id; 45 | static jmethodID set_current_position_method_id; 46 | static jmethodID set_current_state_method_id; 47 | static jmethodID on_gstreamer_initialized_method_id; 48 | 49 | static void setup_client (CustomData * data); 50 | static void destroy_client (CustomData * data); 51 | 52 | /* 53 | * Private methods 54 | */ 55 | static JNIEnv * 56 | attach_current_thread (void) 57 | { 58 | JNIEnv *env; 59 | JavaVMAttachArgs args; 60 | 61 | GST_DEBUG ("Attaching thread %p", g_thread_self ()); 62 | args.version = JNI_VERSION_1_4; 63 | args.name = NULL; 64 | args.group = NULL; 65 | 66 | if ((*java_vm)->AttachCurrentThread (java_vm, &env, &args) < 0) { 67 | GST_ERROR ("Failed to attach current thread"); 68 | return NULL; 69 | } 70 | 71 | return env; 72 | } 73 | 74 | static void 75 | detach_current_thread (void *env) 76 | { 77 | GST_DEBUG ("Detaching thread %p", g_thread_self ()); 78 | (*java_vm)->DetachCurrentThread (java_vm); 79 | } 80 | 81 | static JNIEnv * 82 | get_jni_env (void) 83 | { 84 | JNIEnv *env; 85 | 86 | if ((env = pthread_getspecific (current_jni_env)) == NULL) { 87 | env = attach_current_thread (); 88 | pthread_setspecific (current_jni_env, env); 89 | } 90 | 91 | return env; 92 | } 93 | 94 | static void 95 | set_ui_message (const gchar * message, CustomData * data) 96 | { 97 | JNIEnv *env = get_jni_env (); 98 | GST_DEBUG ("Setting message to: %s", message); 99 | jstring jmessage = (*env)->NewStringUTF (env, message); 100 | (*env)->CallVoidMethod (env, data->app, set_message_method_id, jmessage); 101 | if ((*env)->ExceptionCheck (env)) { 102 | GST_ERROR ("Failed to call Java method"); 103 | (*env)->ExceptionClear (env); 104 | } 105 | (*env)->DeleteLocalRef (env, jmessage); 106 | } 107 | 108 | static void 109 | set_current_ui_position (gint position, gint duration, CustomData * data) 110 | { 111 | JNIEnv *env = get_jni_env (); 112 | // GST_DEBUG ("Setting current position/duration to: %d / %d (ms)", position, duration); 113 | (*env)->CallVoidMethod (env, data->app, set_current_position_method_id, 114 | position, duration); 115 | if ((*env)->ExceptionCheck (env)) { 116 | GST_ERROR ("Failed to call Java method"); 117 | (*env)->ExceptionClear (env); 118 | } 119 | } 120 | 121 | static gboolean 122 | refresh_ui (CustomData * data) 123 | { 124 | GstFormat fmt = GST_FORMAT_TIME; 125 | gint64 current = -1; 126 | 127 | /* We do not want to update anything unless we have a working pipeline */ 128 | if (!data || !data->client || !data->client->player 129 | || !aur_client_is_enabled (data->client)) 130 | return TRUE; 131 | 132 | /* If we didn't know it yet, query the stream duration */ 133 | if (!GST_CLOCK_TIME_IS_VALID (data->duration)) { 134 | if (!gst_element_query_duration (data->client->player, fmt, 135 | &data->duration)) { 136 | GST_WARNING ("Could not query current duration"); 137 | } 138 | } 139 | 140 | if (gst_element_query_position (data->client->player, fmt, &data->position)) { 141 | /* Java expects these values in milliseconds, and Gst provides nanoseconds */ 142 | set_current_ui_position (data->position / GST_MSECOND, 143 | data->duration / GST_MSECOND, data); 144 | } 145 | return TRUE; 146 | } 147 | 148 | static void 149 | error_cb (GstBus * bus, GstMessage * msg, CustomData * data) 150 | { 151 | GError *err; 152 | gchar *debug_info; 153 | gchar *message_string; 154 | 155 | gst_message_parse_error (msg, &err, &debug_info); 156 | message_string = 157 | g_strdup_printf ("Error received from element %s: %s", 158 | GST_OBJECT_NAME (msg->src), err->message); 159 | g_clear_error (&err); 160 | g_free (debug_info); 161 | set_ui_message (message_string, data); 162 | g_free (message_string); 163 | } 164 | 165 | static void 166 | duration_cb (GstBus * bus, GstMessage * msg, CustomData * data) 167 | { 168 | data->duration = GST_CLOCK_TIME_NONE; 169 | } 170 | 171 | static void 172 | check_initialization_complete (CustomData * data) 173 | { 174 | JNIEnv *env = get_jni_env (); 175 | /* Check if all conditions are met to report GStreamer as initialized. 176 | * These conditions will change depending on the application */ 177 | if (!data->initialized && data->native_window && data->main_loop) { 178 | GST_DEBUG 179 | ("Initialization complete, notifying application. native_window:%p main_loop:%p", 180 | data->native_window, data->main_loop); 181 | if (data->client && data->client->player) { 182 | GST_DEBUG 183 | ("Pipeline already created, notifying it about the native window."); 184 | gst_video_overlay_set_window_handle (GST_VIDEO_OVERLAY (data->client-> 185 | player), (guintptr) data->native_window); 186 | } else { 187 | GST_DEBUG 188 | ("Pipeline not created yet, it will later be notified about the native window."); 189 | } 190 | 191 | (*env)->CallVoidMethod (env, data->app, on_gstreamer_initialized_method_id); 192 | if ((*env)->ExceptionCheck (env)) { 193 | GST_ERROR ("Failed to call Java method"); 194 | (*env)->ExceptionDescribe (env); 195 | (*env)->ExceptionClear (env); 196 | } 197 | data->initialized = TRUE; 198 | } 199 | } 200 | 201 | static void 202 | player_created (G_GNUC_UNUSED AurClient *client, GstElement *playbin, 203 | CustomData *data) 204 | { 205 | GstBus *bus; 206 | 207 | gst_video_overlay_set_window_handle (GST_VIDEO_OVERLAY (playbin), 208 | (guintptr) data->native_window); 209 | 210 | /* Instruct the bus to emit signals for each received message, and connect to the interesting signals */ 211 | bus = gst_element_get_bus (data->client->player); 212 | g_signal_connect (G_OBJECT (bus), "message::error", (GCallback) error_cb, 213 | data); 214 | g_signal_connect (G_OBJECT (bus), "message::duration", 215 | (GCallback) duration_cb, data); 216 | gst_object_unref (bus); 217 | } 218 | 219 | static void 220 | setup_client (CustomData * data) 221 | { 222 | if (data->client != NULL) 223 | return; 224 | 225 | data->client = 226 | aur_client_new (data->context, data->server, AUR_CLIENT_PLAYER); 227 | 228 | if (data->native_window) { 229 | GST_DEBUG 230 | ("Native window already received, notifying the pipeline about it."); 231 | g_signal_connect (data->client, "player-created", 232 | G_CALLBACK(player_created), data); 233 | } 234 | 235 | } 236 | 237 | static void 238 | destroy_client (CustomData * data) 239 | { 240 | if (data->client == NULL) 241 | return; 242 | 243 | GST_DEBUG ("Destroying client"); 244 | g_object_unref (data->client); 245 | data->client = NULL; 246 | } 247 | 248 | static void * 249 | app_function (void *userdata) 250 | { 251 | JavaVMAttachArgs args; 252 | CustomData *data = (CustomData *) userdata; 253 | GSource *timeout_source; 254 | 255 | GST_DEBUG ("Creating pipeline in CustomData at %p", data); 256 | 257 | /* create our own GLib Main Context, so we do not interfere with other libraries using GLib */ 258 | data->context = g_main_context_new (); 259 | g_main_context_push_thread_default(data->context); 260 | 261 | /* Register a function that GLib will call 4 times per second */ 262 | timeout_source = g_timeout_source_new (250); 263 | g_source_set_callback (timeout_source, (GSourceFunc) refresh_ui, data, NULL); 264 | g_source_attach (timeout_source, data->context); 265 | g_source_unref (timeout_source); 266 | 267 | /* Create a GLib Main Loop and set it to run */ 268 | GST_DEBUG ("Entering main loop... (CustomData:%p)", data); 269 | data->main_loop = g_main_loop_new (data->context, FALSE); 270 | check_initialization_complete (data); 271 | g_main_loop_run (data->main_loop); 272 | GST_DEBUG ("Exited main loop"); 273 | g_main_loop_unref (data->main_loop); 274 | data->main_loop = NULL; 275 | 276 | /* Free resources */ 277 | destroy_client (data); 278 | 279 | g_main_context_pop_thread_default(data->context); 280 | g_main_context_unref (data->context); 281 | 282 | return NULL; 283 | } 284 | 285 | /* 286 | * Java Bindings 287 | */ 288 | static void 289 | gst_native_init (JNIEnv * env, jobject thiz) 290 | { 291 | CustomData *data = g_new0 (CustomData, 1); 292 | data->duration = GST_CLOCK_TIME_NONE; 293 | SET_CUSTOM_DATA (env, thiz, custom_data_field_id, data); 294 | GST_DEBUG ("Created CustomData at %p", data); 295 | data->app = (*env)->NewGlobalRef (env, thiz); 296 | GST_DEBUG ("Created GlobalRef for app object at %p", data->app); 297 | pthread_create (&gst_app_thread, NULL, &app_function, data); 298 | } 299 | 300 | static void 301 | gst_native_finalize (JNIEnv * env, jobject thiz) 302 | { 303 | CustomData *data = GET_CUSTOM_DATA (env, thiz, custom_data_field_id); 304 | if (!data) 305 | return; 306 | GST_DEBUG ("Quitting main loop..."); 307 | g_main_loop_quit (data->main_loop); 308 | GST_DEBUG ("Waiting for thread to finish..."); 309 | pthread_join (gst_app_thread, NULL); 310 | GST_DEBUG ("Deleting GlobalRef for app object at %p", data->app); 311 | (*env)->DeleteGlobalRef (env, data->app); 312 | GST_DEBUG ("Freeing CustomData at %p", data); 313 | g_free (data->server); 314 | g_free (data); 315 | SET_CUSTOM_DATA (env, thiz, custom_data_field_id, NULL); 316 | GST_DEBUG ("Done finalizing"); 317 | } 318 | 319 | static void 320 | gst_native_play (JNIEnv * env, jobject thiz, jstring serverAddress) 321 | { 322 | CustomData *data = GET_CUSTOM_DATA (env, thiz, custom_data_field_id); 323 | const gchar *s; 324 | 325 | if (!data) 326 | return; 327 | 328 | s = (*env)->GetStringUTFChars (env, serverAddress, 0); 329 | if (s && *s) { 330 | g_free (data->server); 331 | data->server = g_strdup (s); 332 | } 333 | 334 | GST_DEBUG ("Starting client (server %s)", data->server); 335 | 336 | if (data->client) 337 | return; 338 | 339 | setup_client (data); 340 | } 341 | 342 | static void 343 | gst_native_pause (JNIEnv * env, jobject thiz) 344 | { 345 | CustomData *data = GET_CUSTOM_DATA (env, thiz, custom_data_field_id); 346 | 347 | if (!data) 348 | return; 349 | 350 | GST_DEBUG ("Stopping client"); 351 | 352 | if (!data->client) 353 | return; 354 | 355 | destroy_client (data); 356 | } 357 | 358 | static jboolean 359 | gst_class_init (JNIEnv * env, jclass klass) 360 | { 361 | custom_data_field_id = 362 | (*env)->GetFieldID (env, klass, "native_custom_data", "J"); 363 | GST_DEBUG ("The FieldID for the native_custom_data field is %p", 364 | custom_data_field_id); 365 | set_message_method_id = 366 | (*env)->GetMethodID (env, klass, "setMessage", "(Ljava/lang/String;)V"); 367 | GST_DEBUG ("The MethodID for the setMessage method is %p", 368 | set_message_method_id); 369 | set_current_position_method_id = 370 | (*env)->GetMethodID (env, klass, "setCurrentPosition", "(II)V"); 371 | GST_DEBUG ("The MethodID for the setCurrentPosition method is %p", 372 | set_current_position_method_id); 373 | on_gstreamer_initialized_method_id = 374 | (*env)->GetMethodID (env, klass, "onGStreamerInitialized", "()V"); 375 | GST_DEBUG ("The MethodID for the onGStreamerInitialized method is %p", 376 | on_gstreamer_initialized_method_id); 377 | set_current_state_method_id = 378 | (*env)->GetMethodID (env, klass, "setCurrentState", "(I)V"); 379 | GST_DEBUG ("The MethodID for the setCurrentState method is %p", 380 | set_current_state_method_id); 381 | 382 | if (!custom_data_field_id || !set_message_method_id 383 | || !set_current_position_method_id 384 | || !on_gstreamer_initialized_method_id || !set_current_state_method_id) { 385 | GST_ERROR 386 | ("The calling class does not implement all necessary interface methods"); 387 | return JNI_FALSE; 388 | } 389 | return JNI_TRUE; 390 | } 391 | 392 | static void 393 | gst_native_surface_init (JNIEnv * env, jobject thiz, jobject surface) 394 | { 395 | CustomData *data = GET_CUSTOM_DATA (env, thiz, custom_data_field_id); 396 | if (!data) 397 | return; 398 | GST_DEBUG ("Received surface %p", surface); 399 | if (data->native_window) { 400 | GST_DEBUG ("Releasing previous native window %p", data->native_window); 401 | ANativeWindow_release (data->native_window); 402 | } 403 | data->native_window = ANativeWindow_fromSurface (env, surface); 404 | GST_DEBUG ("Got Native Window %p", data->native_window); 405 | 406 | check_initialization_complete (data); 407 | } 408 | 409 | static void 410 | gst_native_surface_finalize (JNIEnv * env, jobject thiz) 411 | { 412 | CustomData *data = GET_CUSTOM_DATA (env, thiz, custom_data_field_id); 413 | if (!data) { 414 | GST_WARNING 415 | ("Received surface finalize but there is no CustomData. Ignoring."); 416 | return; 417 | } 418 | GST_DEBUG ("Releasing Native Window %p", data->native_window); 419 | 420 | if (data->client) { 421 | if (data->client->player) 422 | gst_video_overlay_set_window_handle (GST_VIDEO_OVERLAY (data->client-> 423 | player), (guintptr) NULL); 424 | destroy_client (data); 425 | } 426 | 427 | ANativeWindow_release (data->native_window); 428 | data->native_window = NULL; 429 | } 430 | 431 | static JNINativeMethod native_methods[] = { 432 | {"nativeInit", "()V", (void *) gst_native_init}, 433 | {"nativeFinalize", "()V", (void *) gst_native_finalize}, 434 | {"nativePlay", "(Ljava/lang/String;)V", (void *) gst_native_play}, 435 | {"nativePause", "()V", (void *) gst_native_pause}, 436 | {"classInit", "()Z", (void *) gst_class_init}, 437 | {"nativeSurfaceInit", "(Ljava/lang/Object;)V", 438 | (void *) gst_native_surface_init}, 439 | {"nativeSurfaceFinalize", "()V", (void *) gst_native_surface_finalize} 440 | }; 441 | 442 | jint 443 | JNI_OnLoad (JavaVM * vm, void *reserved) 444 | { 445 | JNIEnv *env = NULL; 446 | 447 | GST_DEBUG_CATEGORY_INIT (debug_category, "android-aurena", 0, 448 | "Android Aurena"); 449 | 450 | java_vm = vm; 451 | 452 | if ((*vm)->GetEnv (vm, (void **) &env, JNI_VERSION_1_4) != JNI_OK) { 453 | GST_ERROR ("Could not retrieve JNIEnv"); 454 | return 0; 455 | } 456 | jclass klass = (*env)->FindClass (env, 457 | "net/noraisin/android_aurena/AndroidAurena"); 458 | (*env)->RegisterNatives (env, klass, native_methods, 459 | G_N_ELEMENTS (native_methods)); 460 | 461 | pthread_key_create (¤t_jni_env, detach_current_thread); 462 | 463 | return JNI_VERSION_1_4; 464 | } 465 | -------------------------------------------------------------------------------- /android/res/drawable-ldpi/aurena.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thaytan/aurena/ccc0530a57edf8e86729abaf378779d2e689aabf/android/res/drawable-ldpi/aurena.png -------------------------------------------------------------------------------- /android/res/drawable-xhdpi/aurena.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thaytan/aurena/ccc0530a57edf8e86729abaf378779d2e689aabf/android/res/drawable-xhdpi/aurena.png -------------------------------------------------------------------------------- /android/res/layout/main.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 11 | 12 | 19 | 20 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /android/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | Android Aurena 4 | Play 5 | Stop 6 | 7 | -------------------------------------------------------------------------------- /android/src/net/noraisin/android_aurena/AndroidAurena.java: -------------------------------------------------------------------------------- 1 | package net.noraisin.android_aurena; 2 | 3 | import java.text.SimpleDateFormat; 4 | import java.util.Date; 5 | import java.util.TimeZone; 6 | 7 | import org.freedesktop.gstreamer.GStreamer; 8 | 9 | import android.app.Activity; 10 | import android.content.Context; 11 | import android.util.Log; 12 | import android.os.Bundle; 13 | import android.os.Environment; 14 | import android.os.PowerManager; 15 | import android.view.SurfaceHolder; 16 | import android.view.SurfaceView; 17 | import android.view.View; 18 | import android.view.View.OnClickListener; 19 | import android.widget.ImageButton; 20 | import android.widget.TextView; 21 | import android.widget.Toast; 22 | 23 | import android.net.nsd.NsdServiceInfo; 24 | import android.net.nsd.NsdManager; 25 | 26 | public class AndroidAurena extends Activity implements SurfaceHolder.Callback { 27 | private static native boolean classInit(); 28 | private native void nativeInit(); 29 | private native void nativeFinalize(); 30 | private native void nativePlay(String server); 31 | private native void nativePause(); 32 | private native void nativeSurfaceInit(Object surface); 33 | private native void nativeSurfaceFinalize(); 34 | private long native_custom_data; 35 | 36 | private int position; 37 | private int duration; 38 | private PowerManager.WakeLock wake_lock; 39 | 40 | /* mDNS members */ 41 | NsdManager mNsdManager; 42 | NsdManager.ResolveListener mResolveListener; 43 | NsdManager.DiscoveryListener mDiscoveryListener; 44 | NsdServiceInfo mService; 45 | 46 | private static final String SERVICE_TYPE = "_aurena._tcp."; 47 | private static final String TAG = "GStreamer"; 48 | 49 | /* Called when the activity is first created. */ 50 | @Override 51 | public void onCreate(Bundle savedInstanceState) 52 | { 53 | super.onCreate(savedInstanceState); 54 | 55 | mNsdManager = (NsdManager) getSystemService(Context.NSD_SERVICE); 56 | initializeDiscoveryListener(); 57 | initializeResolveListener(); 58 | 59 | try { 60 | GStreamer.init(this); 61 | } catch (Exception e) { 62 | Toast.makeText(this, e.getMessage(), Toast.LENGTH_LONG).show(); 63 | finish(); 64 | return; 65 | } 66 | 67 | setContentView(R.layout.main); 68 | 69 | PowerManager pm = (PowerManager) getSystemService(Context.POWER_SERVICE); 70 | wake_lock = pm.newWakeLock(PowerManager.FULL_WAKE_LOCK, "Android Aurena"); 71 | wake_lock.setReferenceCounted(false); 72 | 73 | SurfaceView sv = (SurfaceView) this.findViewById(R.id.surface_video); 74 | SurfaceHolder sh = sv.getHolder(); 75 | sh.addCallback(this); 76 | 77 | nativeInit(); 78 | 79 | wake_lock.acquire(); 80 | } 81 | 82 | protected void onDestroy() { 83 | nativeFinalize(); 84 | if (wake_lock.isHeld()) 85 | wake_lock.release(); 86 | super.onDestroy(); 87 | } 88 | 89 | /* Called from native code */ 90 | private void setMessage(final String message) { 91 | final TextView tv = (TextView) this.findViewById(R.id.textview_message); 92 | runOnUiThread (new Runnable() { 93 | public void run() { 94 | /* Disable until we have got rid of non-fatal errors */ 95 | /*tv.setText(message);*/ 96 | } 97 | }); 98 | } 99 | 100 | /* Called from native code */ 101 | private void onGStreamerInitialized () { 102 | mNsdManager.discoverServices( 103 | SERVICE_TYPE, NsdManager.PROTOCOL_DNS_SD, mDiscoveryListener); 104 | } 105 | 106 | /* The text widget acts as an slave for the seek bar, so it reflects what the seek bar shows, whether 107 | * it is an actual pipeline position or the position the user is currently dragging to. 108 | */ 109 | private void updateTimeWidget () { 110 | final TextView tv = (TextView) this.findViewById(R.id.textview_time); 111 | 112 | SimpleDateFormat df = new SimpleDateFormat("HH:mm:ss"); 113 | df.setTimeZone(TimeZone.getTimeZone("UTC")); 114 | final String message = df.format(new Date (position)) + " / " + df.format(new Date (duration)); 115 | tv.setText(message); 116 | } 117 | 118 | /* Called from native code */ 119 | private void setCurrentPosition(final int position, final int duration) { 120 | this.position = position; 121 | this.duration = duration; 122 | 123 | runOnUiThread (new Runnable() { 124 | public void run() { 125 | updateTimeWidget(); 126 | } 127 | }); 128 | } 129 | 130 | /* Called from native code */ 131 | private void setCurrentState (int state) { 132 | Log.d (TAG, "State has changed to " + state); 133 | switch (state) { 134 | case 1: 135 | setMessage ("NULL"); 136 | break; 137 | case 2: 138 | setMessage ("READY"); 139 | break; 140 | case 3: 141 | setMessage ("PAUSED"); 142 | break; 143 | case 4: 144 | setMessage ("PLAYING"); 145 | break; 146 | } 147 | } 148 | 149 | static { 150 | System.loadLibrary("gstreamer_android"); 151 | System.loadLibrary("android-aurena"); 152 | classInit(); 153 | } 154 | 155 | public void surfaceChanged(SurfaceHolder holder, int format, int width, 156 | int height) { 157 | Log.d(TAG, "Surface changed to format " + format + " width " 158 | + width + " height " + height); 159 | nativeSurfaceInit (holder.getSurface()); 160 | } 161 | 162 | public void surfaceCreated(SurfaceHolder holder) { 163 | Log.d(TAG, "Surface created: " + holder.getSurface()); 164 | } 165 | 166 | public void surfaceDestroyed(SurfaceHolder holder) { 167 | Log.d(TAG, "Surface destroyed"); 168 | nativeSurfaceFinalize (); 169 | } 170 | 171 | // mDNS Discovery 172 | public void initializeDiscoveryListener() { 173 | mDiscoveryListener = new NsdManager.DiscoveryListener() { 174 | 175 | @Override 176 | public void onDiscoveryStarted(String regType) { 177 | Log.d(TAG, "Service discovery started"); 178 | } 179 | 180 | @Override 181 | public void onServiceFound(NsdServiceInfo service) { 182 | Log.d(TAG, "Service discovery success" + service); 183 | if (!service.getServiceType().equals(SERVICE_TYPE)) { 184 | Log.d(TAG, "Unknown Service Type: " + service.getServiceType()); 185 | } else { 186 | mNsdManager.resolveService(service, mResolveListener); 187 | } 188 | } 189 | 190 | @Override 191 | public void onServiceLost(NsdServiceInfo service) { 192 | Log.e(TAG, "service lost" + service); 193 | if (mService == service) { 194 | mService = null; 195 | } 196 | } 197 | 198 | @Override 199 | public void onDiscoveryStopped(String serviceType) { 200 | Log.i(TAG, "Discovery stopped: " + serviceType); 201 | } 202 | 203 | @Override 204 | public void onStartDiscoveryFailed(String serviceType, int errorCode) { 205 | Log.e(TAG, "Discovery failed: Error code:" + errorCode); 206 | // mNsdManager.stopServiceDiscovery(this); 207 | } 208 | 209 | @Override 210 | public void onStopDiscoveryFailed(String serviceType, int errorCode) { 211 | Log.e(TAG, "Discovery failed: Error code:" + errorCode); 212 | mNsdManager.stopServiceDiscovery(this); 213 | } 214 | }; 215 | } 216 | 217 | public void initializeResolveListener() { 218 | mResolveListener = new NsdManager.ResolveListener() { 219 | 220 | @Override 221 | public void onResolveFailed(NsdServiceInfo serviceInfo, int errorCode) { 222 | Log.e(TAG, "Resolve failed" + errorCode); 223 | } 224 | 225 | @Override 226 | public void onServiceResolved(NsdServiceInfo serviceInfo) { 227 | Log.e(TAG, "Resolve Succeeded. " + serviceInfo); 228 | 229 | mService = serviceInfo; 230 | String server = mService.getHost().getHostAddress() + ":" + mService.getPort(); 231 | 232 | Log.d(TAG, "Connecting to server: " + server); 233 | nativePause (); 234 | nativePlay (server); 235 | } 236 | }; 237 | } 238 | } 239 | -------------------------------------------------------------------------------- /autogen.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | autoreconf -i && ./configure "$@" 4 | -------------------------------------------------------------------------------- /configure.ac: -------------------------------------------------------------------------------- 1 | AC_PREREQ([2.68]) 2 | AC_INIT([aurena], [0.9], [thaytan@noraisin.net]) 3 | 4 | AM_INIT_AUTOMAKE([-Wno-portability 1.11 no-dist-gzip dist-bzip2 dist-xz tar-ustar]) 5 | 6 | AM_SILENT_RULES([yes]) 7 | 8 | AM_MAINTAINER_MODE([enable]) 9 | 10 | AC_CONFIG_SRCDIR([src/aurena-server.c]) 11 | AM_CONFIG_HEADER([config.h]) 12 | 13 | AC_CANONICAL_HOST 14 | 15 | dnl Find C compiler with C99 16 | AC_PROG_CC 17 | AC_PROG_CC_STDC 18 | 19 | LT_INIT 20 | 21 | PKG_PROG_PKG_CONFIG([0.22]) 22 | PKG_CHECK_MODULES(AUR_COMMON, [gobject-2.0 glib-2.0 >= 2.30 gio-2.0 avahi-client avahi-glib >= 0.6.24 json-glib-1.0 libsoup-2.4 >= 2.26.1]) 23 | AC_DEFINE([HAVE_AVAHI], 1, [Defined if compiling with Avahi support]) 24 | 25 | AC_MSG_NOTICE([Checking for GStreamer 1.0]) 26 | PKG_CHECK_MODULES(GST_1_0, [gstreamer-1.0 gstreamer-net-1.0 gstreamer-tag-1.0], [HAVE_GST_1_0=yes], [HAVE_GST_1_0=no]) 27 | if test "x$HAVE_GST_1_0" = "xyes"; then 28 | GST_CFLAGS="$GST_1_0_CFLAGS -DGST_USE_UNSTABLE_API" 29 | GST_LIBS="$GST_1_0_LIBS" 30 | else 31 | AC_MSG_ERROR([GStreamer 1.0 required]) 32 | fi 33 | 34 | AC_ARG_WITH([rtsp], AS_HELP_STRING([--with-rtsp], [Build with the GStreamer RTSP server]), [], [with_rtsp=no]) 35 | 36 | AC_MSG_CHECKING([whether for RTSP support]) 37 | AS_IF([test "x$with_rtsp" != "xno"], [ 38 | AS_IF([test "x$HAVE_GST_1_0" = "xyes"], [ 39 | PKG_CHECK_MODULES(GST_RTSP, [gstreamer-rtsp-server-1.0], [HAVE_GST_RTSP=yes], [HAVE_GST_RTSP=no]) 40 | ],[ 41 | PKG_CHECK_MODULES(GST_RTSP, [gst-rtsp-server-0.10], [HAVE_GST_RTSP=yes], [HAVE_GST_RTSP=no]) 42 | ]) 43 | AS_IF([test "x$HAVE_GST_RTSP" = "xyes"], 44 | [AC_MSG_RESULT([yes])], 45 | [ 46 | AC_MSG_RESULT([not available]) 47 | AC_MSG_ERROR([Requested build with RTSP support requires gst-rtsp-server]) 48 | ] 49 | ) 50 | ],[ AC_MSG_RESULT([disabled]) ]) 51 | 52 | AC_MSG_NOTICE([Check for GTK+ 3.0 requirements]) 53 | PKG_CHECK_MODULES(GTKAPPS, [gtk+-3.0], [BUILD_GTKAPPS=yes], 54 | [BUILDS_GTKAPP=no]) 55 | AM_CONDITIONAL(BUILD_GTKAPPS, test "x$BUILD_GTKAPPS" = "xyes") 56 | if test "x$BUILD_GTKAPPS" = "xyes"; then 57 | if test "x$HAVE_GST_1_0" = "xyes"; then 58 | GTKAPPS_LIBS="$GTKAPPS_LIBS -lgstvideo-1.0" 59 | else 60 | GTKAPPS_LIBS="$GTKAPPS_LIBS -lgstinterfaces-0.10" 61 | fi 62 | AC_SUBST([GTKAPP_CFLAGS]) 63 | AC_SUBST([GTKAPPS_LIBS]) 64 | fi 65 | 66 | AC_MSG_NOTICE([Checking whether to build server (sqlite >= 3.3, glib >= 2.32)]) 67 | PKG_CHECK_MODULES(AUR_SERVER, [sqlite3 >= 3.3 glib-2.0 >= 2.32 libsoup-2.4 > 2.48 ], 68 | [BUILD_AUR_SERVER=yes], [BUILD_AUR_SERVER=no]) 69 | AM_CONDITIONAL(BUILD_AUR_SERVER, test "x$BUILD_AUR_SERVER" = "xyes") 70 | 71 | if test "x$HAVE_GST_RTSP" = "xyes"; then 72 | AC_DEFINE([HAVE_GST_RTSP], 1, [Define if gstreamer-rtsp-server is available]) 73 | fi 74 | 75 | AC_SUBST([GST_CFLAGS]) 76 | AC_SUBST([GST_LIBS]) 77 | 78 | EXTRA_CFLAGS="-Wall -Wextra" 79 | AC_SUBST([EXTRA_CFLAGS]) 80 | 81 | AC_CONFIG_FILES([ 82 | Makefile 83 | src/Makefile 84 | src/client/Makefile 85 | src/server/Makefile 86 | data/Makefile 87 | data/htdocs/Makefile 88 | test/Makefile 89 | ]) 90 | AC_OUTPUT 91 | -------------------------------------------------------------------------------- /content/config: -------------------------------------------------------------------------------- 1 | [server] 2 | port=5457 3 | playlist=playlist.txt 4 | database=media.db 5 | -------------------------------------------------------------------------------- /data/Makefile.am: -------------------------------------------------------------------------------- 1 | SUBDIRS = htdocs 2 | 3 | controlleruidir = $(datadir)/aurena 4 | controllerui_DATA = simple-controller-client.ui simple-controller.ui 5 | 6 | EXTRA_DIST = $(controllerui_DATA) 7 | -------------------------------------------------------------------------------- /data/htdocs/Makefile.am: -------------------------------------------------------------------------------- 1 | serverdatadir = $(datadir)/aurena/htdocs 2 | nobase_serverdata_DATA = \ 3 | icon.png \ 4 | js/jquery-ui-1.8.22.custom.min.js \ 5 | js/aurena.js \ 6 | js/jquery-1.7.2.min.js \ 7 | index.html \ 8 | css/smoothness/jquery-ui-1.8.22.custom.css \ 9 | css/smoothness/images/ui-bg_glass_55_fbf9ee_1x400.png \ 10 | css/smoothness/images/ui-bg_flat_0_aaaaaa_40x100.png \ 11 | css/smoothness/images/ui-icons_222222_256x240.png \ 12 | css/smoothness/images/ui-bg_flat_75_ffffff_40x100.png \ 13 | css/smoothness/images/ui-icons_2e83ff_256x240.png \ 14 | css/smoothness/images/ui-bg_glass_95_fef1ec_1x400.png \ 15 | css/smoothness/images/ui-bg_highlight-soft_75_cccccc_1x100.png \ 16 | css/smoothness/images/ui-bg_glass_75_e6e6e6_1x400.png \ 17 | css/smoothness/images/ui-bg_glass_75_dadada_1x400.png \ 18 | css/smoothness/images/ui-icons_454545_256x240.png \ 19 | css/smoothness/images/ui-icons_cd0a0a_256x240.png \ 20 | css/smoothness/images/ui-bg_glass_65_ffffff_1x400.png \ 21 | css/smoothness/images/ui-icons_888888_256x240.png \ 22 | css/aurena.css 23 | 24 | EXTRA_DIST = $(nobase_serverdata_DATA) 25 | -------------------------------------------------------------------------------- /data/htdocs/css/aurena.css: -------------------------------------------------------------------------------- 1 | html { 2 | background-color: #000000; 3 | } 4 | 5 | #player { 6 | width: 640; 7 | background-color: #101010; 8 | padding: 10px 15px; 9 | color: white; 10 | position: relative; 11 | } 12 | 13 | #header { font-size: 130%; display: inline; } 14 | #connstatus { position: absolute; right: 10px; top: 0px; } 15 | #playstate { display: inline; margin: 10px; } 16 | #mastervollabel, #mastervolval { display: inline; } 17 | #mastervol, .inline { display: inline; padding-top: 10px; margin: 10px; } 18 | #mastervolslider, #nowplayingdiv { display: inline-block; width: 80%; } 19 | #nowplayingdiv { padding-top: 15px; } 20 | #controls { margin: 10px; } 21 | 22 | #debug { display: none; } 23 | -------------------------------------------------------------------------------- /data/htdocs/css/smoothness/images/ui-bg_flat_0_aaaaaa_40x100.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thaytan/aurena/ccc0530a57edf8e86729abaf378779d2e689aabf/data/htdocs/css/smoothness/images/ui-bg_flat_0_aaaaaa_40x100.png -------------------------------------------------------------------------------- /data/htdocs/css/smoothness/images/ui-bg_flat_75_ffffff_40x100.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thaytan/aurena/ccc0530a57edf8e86729abaf378779d2e689aabf/data/htdocs/css/smoothness/images/ui-bg_flat_75_ffffff_40x100.png -------------------------------------------------------------------------------- /data/htdocs/css/smoothness/images/ui-bg_glass_55_fbf9ee_1x400.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thaytan/aurena/ccc0530a57edf8e86729abaf378779d2e689aabf/data/htdocs/css/smoothness/images/ui-bg_glass_55_fbf9ee_1x400.png -------------------------------------------------------------------------------- /data/htdocs/css/smoothness/images/ui-bg_glass_65_ffffff_1x400.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thaytan/aurena/ccc0530a57edf8e86729abaf378779d2e689aabf/data/htdocs/css/smoothness/images/ui-bg_glass_65_ffffff_1x400.png -------------------------------------------------------------------------------- /data/htdocs/css/smoothness/images/ui-bg_glass_75_dadada_1x400.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thaytan/aurena/ccc0530a57edf8e86729abaf378779d2e689aabf/data/htdocs/css/smoothness/images/ui-bg_glass_75_dadada_1x400.png -------------------------------------------------------------------------------- /data/htdocs/css/smoothness/images/ui-bg_glass_75_e6e6e6_1x400.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thaytan/aurena/ccc0530a57edf8e86729abaf378779d2e689aabf/data/htdocs/css/smoothness/images/ui-bg_glass_75_e6e6e6_1x400.png -------------------------------------------------------------------------------- /data/htdocs/css/smoothness/images/ui-bg_glass_95_fef1ec_1x400.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thaytan/aurena/ccc0530a57edf8e86729abaf378779d2e689aabf/data/htdocs/css/smoothness/images/ui-bg_glass_95_fef1ec_1x400.png -------------------------------------------------------------------------------- /data/htdocs/css/smoothness/images/ui-bg_highlight-soft_75_cccccc_1x100.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thaytan/aurena/ccc0530a57edf8e86729abaf378779d2e689aabf/data/htdocs/css/smoothness/images/ui-bg_highlight-soft_75_cccccc_1x100.png -------------------------------------------------------------------------------- /data/htdocs/css/smoothness/images/ui-icons_222222_256x240.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thaytan/aurena/ccc0530a57edf8e86729abaf378779d2e689aabf/data/htdocs/css/smoothness/images/ui-icons_222222_256x240.png -------------------------------------------------------------------------------- /data/htdocs/css/smoothness/images/ui-icons_2e83ff_256x240.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thaytan/aurena/ccc0530a57edf8e86729abaf378779d2e689aabf/data/htdocs/css/smoothness/images/ui-icons_2e83ff_256x240.png -------------------------------------------------------------------------------- /data/htdocs/css/smoothness/images/ui-icons_454545_256x240.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thaytan/aurena/ccc0530a57edf8e86729abaf378779d2e689aabf/data/htdocs/css/smoothness/images/ui-icons_454545_256x240.png -------------------------------------------------------------------------------- /data/htdocs/css/smoothness/images/ui-icons_888888_256x240.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thaytan/aurena/ccc0530a57edf8e86729abaf378779d2e689aabf/data/htdocs/css/smoothness/images/ui-icons_888888_256x240.png -------------------------------------------------------------------------------- /data/htdocs/css/smoothness/images/ui-icons_cd0a0a_256x240.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thaytan/aurena/ccc0530a57edf8e86729abaf378779d2e689aabf/data/htdocs/css/smoothness/images/ui-icons_cd0a0a_256x240.png -------------------------------------------------------------------------------- /data/htdocs/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thaytan/aurena/ccc0530a57edf8e86729abaf378779d2e689aabf/data/htdocs/icon.png -------------------------------------------------------------------------------- /data/htdocs/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Aurena 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 |
13 | 14 |
15 |
16 |
Volume:
100%
17 |
Now Playing
18 |
19 | 20 |

Connected outputs:

21 |
22 |
23 | Jump to Track: 24 |
25 |
26 |
27 |
28 | 29 |
30 | 31 | 32 | -------------------------------------------------------------------------------- /data/htdocs/js/aurena.js: -------------------------------------------------------------------------------- 1 | aurena = { 2 | 3 | websocket_listener : function() 4 | { 5 | if (aurena.ws != null) 6 | return; 7 | 8 | if (!("WebSocket" in window) && ("MozWebSocket" in window)) 9 | window.WebSocket = window.MozWebSocket; 10 | 11 | if ("WebSocket" in window) 12 | { 13 | var url = document.URL.replace("http", "ws") + "../client/control_events"; 14 | var ws = new WebSocket(url, "aurena"); 15 | aurena.ws = ws; 16 | 17 | ws.onopen = function() 18 | { 19 | $("#connstatus").html("

Connected

"); 20 | }; 21 | ws.onmessage = function (evt) 22 | { 23 | var received_msg = evt.data; 24 | 25 | if (typeof received_msg == typeof("")) { 26 | aurena.handle_event(received_msg); 27 | } 28 | }; 29 | ws.onclose = function() 30 | { 31 | $("#connstatus").html("

Disconnected

"); 32 | aurena.ws = null; 33 | setTimeout(aurena.websocket_listener, 1000); 34 | }; 35 | } 36 | else 37 | { 38 | $("#debug").prepend("

websocket connections not available!

"); 39 | } 40 | }, 41 | update_playstate : function() { 42 | if (aurena.paused) { 43 | $("#playstate").html("Paused"); 44 | } 45 | else { 46 | $("#playstate").html("Playing"); 47 | } 48 | if (aurena.cur_media != 0) 49 | $("#nowplaying").html("track " + aurena.cur_media); 50 | else 51 | $("#nowplaying").html("None"); 52 | }, 53 | 54 | set_vol_slider : function set_vol_slider(client_id, vol, anim) { 55 | var s; 56 | 57 | if (client_id == 0) 58 | s = $("#mastervolslider"); 59 | else 60 | s = $("#volume-" + client_id); 61 | 62 | if (!aurena.sliding && vol != s.slider("value")) { 63 | // $("#debug").prepend("

Setting vol " + vol + "

"); 64 | aurena.slide_update = true; 65 | s.slider("option", "animate", anim); 66 | s.slider("value", vol); 67 | s.slider("option", "animate", true); 68 | aurena.slide_update = false; 69 | } 70 | }, 71 | 72 | add_client_message : function f(client_id, msg) { 73 | c = $("#clientmsgs"); 74 | c.text(c.text() + msg); 75 | }, 76 | 77 | set_client_enable : function f(client_id, enable) { 78 | if (client_id < 1) 79 | return; 80 | var s = $("#enable-" + client_id); 81 | s.attr('checked', enable); 82 | }, 83 | 84 | handle_event : function handle_event(data) { 85 | json = $.parseJSON(data); 86 | switch (json["msg-type"]) { 87 | case "enrol": 88 | var vol = json["volume-level"]; 89 | aurena.paused = json["paused"]; 90 | aurena.cur_media = json["resource-id"]; 91 | 92 | aurena.set_vol_slider (0, vol, false); 93 | aurena.update_playstate(); 94 | break; 95 | case "volume": 96 | var vol = json["level"]; 97 | aurena.set_vol_slider (0, vol, true); 98 | break; 99 | case "client-volume": 100 | var vol = json["level"]; 101 | aurena.set_vol_slider (json["client-id"], vol, true); 102 | break; 103 | case "client-setting": 104 | var en = json["enabled"]; 105 | aurena.set_client_enable (json["client-id"], en); 106 | break; 107 | case "client-message": 108 | aurena.add_client_message(json["client-id"], json["message"]); 109 | break; 110 | case "pause": 111 | aurena.paused = true; 112 | aurena.update_playstate(); 113 | break; 114 | case "play": 115 | aurena.paused = false; 116 | aurena.update_playstate(); 117 | break; 118 | case "set-media": 119 | aurena.paused = json["paused"]; 120 | aurena.cur_media = json["resource-id"]; 121 | aurena.update_playstate(); 122 | break; 123 | case "player-clients-changed": 124 | aurena.update_player_clients(); 125 | break; 126 | default: 127 | $("#debug").prepend("

Received message of type " + json["msg-type"] + ": " + data + "

"); 128 | break; 129 | } 130 | }, 131 | update_player_clients : function () { 132 | function send_enable_val(client_id) { 133 | if (aurena.sendingEnable) 134 | return; 135 | var enabled = 0; 136 | 137 | if ($("#enable-" + client_id).attr('checked')) 138 | enabled = 1; 139 | 140 | aurena.sendingEnable = true; 141 | $.ajax({ 142 | type: 'GET', 143 | url: "../control/setclient", 144 | data: { client_id: client_id, enable: enabled } 145 | }).complete(function() { 146 | aurena.sendingEnable = false; 147 | }); 148 | } 149 | 150 | $.getJSON("../client/player_info", function(data) { 151 | var items = []; 152 | var clients = data['player-clients']; 153 | aurena.clients = clients; 154 | $.each(clients, function(key, val) { 155 | var enable_id = "enable-" + val["client-id"]; 156 | var volume_id = "volume-" + val["client-id"]; 157 | var info = '
  • '; 158 | info += ""; 159 | info += " Client " + val["host"]; 160 | info += "
    "; 161 | info += "
    "; 162 | info += '
  • '; 163 | items.push(info); 164 | // console.log ("Client data " + JSON.stringify(val)); 165 | }); 166 | $("#cliententries").empty().prepend($('
      ', { 167 | html: items.join('') 168 | })); 169 | $.each(clients, function(key, val) { 170 | var client_id = val["client-id"]; 171 | var enable_id = "enable-" + client_id; 172 | var volume_id = "volume-" + client_id; 173 | 174 | $("#" + volume_id).slider({ 175 | animate: true, 176 | min : 0.0, max : 1.5, range : 'true', value : val["volume"], step : 0.01, 177 | start : function(event, ui) { aurena.sliding = true; }, 178 | stop : function(event, ui) { setTimeout(function() { aurena.sliding = false; }, 100); }, 179 | slide : function(cid) { return function(event, ui) { aurena.volChange = true; aurena.send_slider_volume(cid); } }(client_id), 180 | change : function(cid) { return function(event, ui) { aurena.volChange = true; aurena.send_slider_volume(cid); } }(client_id) 181 | }); 182 | $('#volumeval-' + client_id).text(Math.round(val["volume"] * 100).toString() + '%'); 183 | $("#" + enable_id).attr('checked', val["enabled"]).change(function(cid) { return function () { send_enable_val (cid) } }(client_id)); 184 | }); 185 | }); 186 | }, 187 | 188 | send_slider_volume : function send_slider_volume(client_id) { 189 | var s; 190 | 191 | if (client_id == 0) 192 | s= $("#mastervolslider"); 193 | else 194 | s = $("#volume-" + client_id); 195 | 196 | var curVol = s.slider( "option", "value"); 197 | if (client_id == 0) 198 | $('#mastervolval').text(Math.round(curVol * 100).toString() + '%'); 199 | else 200 | $('#volumeval-' + client_id).text(Math.round(curVol * 100).toString() + '%'); 201 | 202 | if (aurena.sendingVol || !aurena.volChange || aurena.slide_update) return; 203 | aurena.sendingVol = true; aurena.volChange = false; 204 | $("#debug").prepend("

      Sending volume " + curVol.toString() + "

      "); 205 | $.ajax({ 206 | type: 'POST', 207 | url: "../control/volume", 208 | data: { level: curVol.toString(), client_id: client_id } 209 | }).complete(function() { 210 | aurena.sendingVol = false; aurena.send_slider_volume(client_id); 211 | }); 212 | }, 213 | playPause : function() { 214 | if (aurena.paused) 215 | aurena.play(); 216 | else 217 | aurena.pause(); 218 | aurena.paused = !aurena.paused; 219 | }, 220 | play : function() { 221 | $.ajax({ url: "../control/play" , type: 'POST'}); 222 | }, 223 | pause : function() { 224 | $.ajax({ url: "../control/pause" , type: 'POST'}); 225 | }, 226 | next : function() { 227 | $.ajax({ url: "../control/next" , type: 'POST'}); 228 | }, 229 | previous : function() { 230 | $.ajax({ url: "../control/previous" , type: 'POST'}); 231 | }, 232 | jumpToTrack : function () { 233 | v = $("#jumptotrackid").val(); 234 | 235 | track=parseInt(v); 236 | if (!isNaN(track) || v.substring(0, 4) == "http") { 237 | $.ajax({ url: "../control/next", type: 'POST', 238 | data: { id: v } }); 239 | } 240 | $("#jumptotrackid").val(""); 241 | }, 242 | init : function() { 243 | aurena.sendingVol = false; 244 | aurena.volChange = false; 245 | 246 | aurena.sliding = false; 247 | aurena.slide_update = false; 248 | aurena.paused = true; 249 | aurena.cur_media = 0; 250 | 251 | $("#mastervolslider").slider({ 252 | animate: true, 253 | min : 0.0, max : 1.5, range : 'true', value : 1.0, step : 0.01, 254 | start : function(event, ui) { //$("#debug").prepend("

      start"); 255 | aurena.sliding = true; }, 256 | stop : function(event, ui) { setTimeout(function() { aurena.sliding = false; }, 100); }, 257 | slide : function(event, ui) { aurena.volChange = true; aurena.send_slider_volume(0); }, 258 | change : function(event, ui) { aurena.volChange = true; aurena.send_slider_volume(0); } 259 | }); 260 | $("#play").click(function() { aurena.play() }); 261 | $("#pause").click(function() { aurena.pause() }); 262 | $("#next").click(function() { aurena.next() }); 263 | $("#jumptrackform").submit(function(e) { aurena.jumpToTrack(); e.preventDefault(); return false; }); 264 | aurena.websocket_listener(); 265 | } 266 | 267 | }; 268 | 269 | $(document).ready(function() { 270 | aurena.init(); 271 | }); 272 | -------------------------------------------------------------------------------- /data/logo.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 19 | 21 | 24 | 28 | 32 | 33 | 44 | 45 | 65 | 67 | 68 | 70 | image/svg+xml 71 | 73 | 74 | 75 | 76 | 77 | 136 | 141 | 150 | 158 | 165 | 166 | 170 | 179 | 180 | 181 | -------------------------------------------------------------------------------- /data/simple-controller-client.ui: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | True 6 | False 7 | 8 | 9 | True 10 | True 11 | 35 12 | 13 | 14 | False 15 | True 16 | 0 17 | 18 | 19 | 20 | 21 | True 22 | True 23 | True 24 | True 25 | none 26 | False 27 | audio-volume-muted 28 | audio-volume-high 29 | audio-volume-low 30 | audio-volume-medium 31 | 32 | 33 | False 34 | True 35 | 1 36 | 37 | 38 | 39 | 40 | True 41 | False 42 | 5 43 | 5 44 | label 45 | 46 | 47 | 48 | 49 | 50 | False 51 | True 52 | 2 53 | 54 | 55 | 56 | 57 | -------------------------------------------------------------------------------- /data/simple-controller.ui: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | True 7 | False 8 | gtk-fullscreen 9 | 10 | 11 | True 12 | False 13 | gtk-media-next 14 | 15 | 16 | True 17 | False 18 | gtk-media-pause 19 | 20 | 21 | True 22 | False 23 | gtk-media-play 24 | 25 | 26 | False 27 | Aurena Controller Client 28 | 29 | 30 | True 31 | False 32 | vertical 33 | 34 | 35 | True 36 | False 37 | 38 | 39 | True 40 | False 41 | Not currently playing 42 | 43 | 44 | 45 | 46 | 47 | False 48 | True 49 | 0 50 | 51 | 52 | 53 | 54 | False 55 | False 56 | 0 57 | 58 | 59 | 60 | 61 | True 62 | False 63 | 64 | 65 | 320 66 | 240 67 | True 68 | False 69 | 250 70 | video-x-generic 71 | 72 | 73 | 74 | 75 | True 76 | True 77 | 1 78 | 79 | 80 | 81 | 82 | True 83 | False 84 | True 85 | 1 86 | False 87 | 88 | 89 | False 90 | True 91 | 2 92 | 93 | 94 | 95 | 96 | True 97 | False 98 | 99 | 100 | True 101 | True 102 | True 103 | fullscreenImage 104 | 105 | 106 | 107 | 108 | False 109 | True 110 | end 111 | 0 112 | 113 | 114 | 115 | 116 | True 117 | False 118 | True 119 | True 120 | True 121 | none 122 | False 123 | vertical 124 | audio-volume-muted 125 | audio-volume-high 126 | audio-volume-low 127 | audio-volume-medium 128 | 129 | 130 | False 131 | False 132 | 133 | 134 | 135 | 136 | False 137 | False 138 | 139 | 140 | 141 | 142 | False 143 | True 144 | end 145 | 1 146 | 147 | 148 | 149 | 150 | True 151 | False 152 | True 153 | True 154 | playImage 155 | 156 | 157 | 158 | False 159 | True 160 | 2 161 | 162 | 163 | 164 | 165 | True 166 | False 167 | Select next file 168 | 169 | 170 | False 171 | True 172 | 3 173 | 174 | 175 | 176 | 177 | True 178 | False 179 | True 180 | True 181 | nextImage 182 | 183 | 184 | False 185 | True 186 | 4 187 | 188 | 189 | 190 | 191 | True 192 | False 193 | 194 | 195 | 300 196 | True 197 | False 198 | False 199 | 200 | 201 | False 202 | True 203 | 1 204 | 205 | 206 | 207 | 208 | True 209 | False 210 | 5 211 | 212 | 213 | 214 | 215 | False 216 | True 217 | 3 218 | 219 | 220 | 221 | 222 | True 223 | False 224 | True 225 | 226 | 227 | 228 | 229 | 230 | True 231 | False 232 | No player connected 233 | 234 | 235 | 236 | 237 | False 238 | True 239 | 4 240 | 241 | 242 | 243 | 244 | 245 | 246 | -------------------------------------------------------------------------------- /src/Makefile.am: -------------------------------------------------------------------------------- 1 | SUBDIRS=client 2 | bin_PROGRAMS = aurena-simple-client 3 | 4 | if BUILD_AUR_SERVER 5 | SUBDIRS += server 6 | bin_PROGRAMS += aurena-server 7 | endif 8 | 9 | if BUILD_GTKAPPS 10 | bin_PROGRAMS += aurena-simple-client-fullscreen aurena-simple-controller 11 | endif 12 | 13 | COMMON_SOURCES = common/aur-json.c common/aur-json.h common/aur-types.h \ 14 | common/aur-websocket-parser.c common/aur-websocket-parser.h 15 | 16 | aurena_server_CPPFLAGS = -I$(top_srcdir) $(AUR_COMMON_CFLAGS) $(AUR_SERVER_CFLAGS) $(GST_RTSP_CFLAGS) $(GST_CFLAGS) $(EXTRA_CFLAGS) 17 | aurena_server_LDADD = server/libaurena_server.la $(AUR_COMMON_LIBS) $(AUR_SERVER_LIBS) $(GST_RTSP_LIBS) $(GST_LIBS) 18 | aurena_server_SOURCES = aurena-server.c $(COMMON_SOURCES) 19 | 20 | aurena_simple_client_SOURCES = simple-client.c $(COMMON_SOURCES) 21 | aurena_simple_client_CPPFLAGS = -I$(top_srcdir) $(AUR_COMMON_CFLAGS) $(GST_CFLAGS) $(EXTRA_CFLAGS) 22 | aurena_simple_client_LDADD = client/libaurena_client.la $(AUR_COMMON_LIBS) $(GST_LIBS) 23 | 24 | 25 | aurena_simple_client_fullscreen_SOURCES = simple-client-fullscreen.c $(COMMON_SOURCES) 26 | aurena_simple_client_fullscreen_CPPFLAGS = $(aurena_simple_client_CPPFLAGS) \ 27 | $(GTKAPPS_CFLAGS) 28 | aurena_simple_client_fullscreen_LDADD = $(aurena_simple_client_LDADD) $(GTKAPPS_LIBS) 29 | 30 | 31 | aurena_simple_controller_SOURCES = simple-controller.c $(COMMON_SOURCES) 32 | aurena_simple_controller_CPPFLAGS = $(aurena_simple_client_CPPFLAGS) \ 33 | $(GTKAPPS_CFLAGS) 34 | aurena_simple_controller_LDADD = $(aurena_simple_client_LDADD) $(GTKAPPS_LIBS) 35 | 36 | EXTRA_DIST = gnome-client.py 37 | DIST_SUBDIRS = client server 38 | -------------------------------------------------------------------------------- /src/aurena-server.c: -------------------------------------------------------------------------------- 1 | /* GStreamer 2 | * Copyright (C) 2012 Jan Schmidt 3 | * 4 | * This library is free software; you can redistribute it and/or 5 | * modify it under the terms of the GNU Library General Public 6 | * License as published by the Free Software Foundation; either 7 | * version 2 of the License, or (at your option) any later version. 8 | * 9 | * This library is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 12 | * Library General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU Library General Public 15 | * License along with this library; if not, write to the 16 | * Free Software Foundation, Inc., 59 Temple Place - Suite 330, 17 | * Boston, MA 02111-1307, USA. 18 | */ 19 | 20 | #ifdef HAVE_CONFIG_H 21 | #include "config.h" 22 | #endif 23 | 24 | #include 25 | #include 26 | #include 27 | 28 | #include "server/aur-manager.h" 29 | 30 | GMainLoop *ml; 31 | 32 | static gboolean 33 | sigint_handler (G_GNUC_UNUSED void *data) 34 | { 35 | g_print ("Exiting...\n"); 36 | g_main_loop_quit (ml); 37 | 38 | return TRUE; 39 | } 40 | 41 | int 42 | main (int argc, char *argv[]) 43 | { 44 | AurManager *manager; 45 | char *config_file = NULL; 46 | 47 | gst_init (&argc, &argv); 48 | 49 | if (argc > 1) 50 | config_file = argv[1]; 51 | 52 | g_unix_signal_add (SIGINT, sigint_handler, NULL); 53 | 54 | manager = aur_manager_new(config_file); 55 | if (manager == NULL) 56 | return -1; 57 | 58 | ml = g_main_loop_new(NULL, FALSE); 59 | g_main_loop_run(ml); 60 | 61 | g_object_unref (manager); 62 | return 0; 63 | } 64 | -------------------------------------------------------------------------------- /src/client/Makefile.am: -------------------------------------------------------------------------------- 1 | noinst_LTLIBRARIES = libaurena_client.la 2 | 3 | libaurena_client_la_SOURCES = \ 4 | aur-client.c aur-client.h 5 | libaurena_client_la_CPPFLAGS = -I$(top_srcdir) $(AUR_COMMON_CFLAGS) $(GST_CFLAGS) $(EXTRA_CFLAGS) 6 | libaurena_client_la_LIBADD = 7 | -------------------------------------------------------------------------------- /src/client/aur-client.h: -------------------------------------------------------------------------------- 1 | /* GStreamer 2 | * Copyright (C) 2012-2014 Jan Schmidt 3 | * 4 | * This library is free software; you can redistribute it and/or 5 | * modify it under the terms of the GNU Library General Public 6 | * License as published by the Free Software Foundation; either 7 | * version 2 of the License, or (at your option) any later version. 8 | * 9 | * This library is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 12 | * Library General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU Library General Public 15 | * License along with this library; if not, write to the 16 | * Free Software Foundation, Inc., 59 Temple Place - Suite 330, 17 | * Boston, MA 02111-1307, USA. 18 | */ 19 | #ifndef __AUR_CLIENT_H__ 20 | #define __AUR_CLIENT_H__ 21 | 22 | #include 23 | #include 24 | #include 25 | #include 26 | 27 | #if HAVE_AVAHI 28 | #include 29 | #include 30 | #include 31 | #include 32 | #include 33 | #endif 34 | 35 | #include "src/common/aur-types.h" 36 | 37 | G_BEGIN_DECLS 38 | 39 | #define AUR_TYPE_CLIENT (aur_client_get_type ()) 40 | 41 | typedef struct _AurClientClass AurClientClass; 42 | 43 | typedef struct 44 | { 45 | guint id; 46 | gchar * host; 47 | gdouble volume; 48 | gboolean enabled; 49 | } AurPlayerInfo; 50 | 51 | typedef enum 52 | { 53 | AUR_CLIENT_NONE = 0, 54 | AUR_CLIENT_PLAYER = (1 << 0), 55 | AUR_CLIENT_CONTROLLER = (1 << 1), 56 | } AurClientFlags; 57 | 58 | struct _AurClient 59 | { 60 | GObject parent; 61 | 62 | AurClientFlags flags; 63 | gdouble volume; 64 | GArray *player_info; 65 | 66 | gboolean enabled; 67 | gboolean paused; 68 | GstClockTime base_time; 69 | GstClockTime position; 70 | gchar *uri; 71 | gchar *language; 72 | 73 | GstClock *net_clock; 74 | gchar *server_host; 75 | gint server_port; 76 | 77 | SoupSession *soup; 78 | SoupMessage *msg; 79 | JsonParser *json; 80 | 81 | GMainContext * context; 82 | 83 | GstElement *player; 84 | GSource *bus_source; 85 | 86 | guint timeout; 87 | guint idle_timeout; 88 | 89 | gboolean connecting; 90 | gboolean was_connected; 91 | gboolean shutting_down; 92 | gchar *connected_server; 93 | gint connected_port; 94 | 95 | #if HAVE_AVAHI 96 | AvahiGLibPoll *glib_poll; 97 | AvahiClient *avahi_client; 98 | AvahiServiceBrowser *avahi_sb; 99 | #endif 100 | }; 101 | 102 | struct _AurClientClass 103 | { 104 | GObjectClass parent; 105 | }; 106 | 107 | GType aur_client_get_type(void); 108 | AurClient *aur_client_new(GMainContext * context, const gchar *server, AurClientFlags flags); 109 | 110 | gboolean aur_client_is_connected (AurClient * client); 111 | gboolean aur_client_is_enabled (AurClient * client); 112 | gboolean aur_client_is_playing (AurClient * client); 113 | void aur_client_set_media (AurClient * client, const gchar * id); 114 | void aur_client_next (AurClient * client, guint id); 115 | void aur_client_play (AurClient * client); 116 | void aur_client_pause (AurClient * client); 117 | void aur_client_seek (AurClient * client, GstClockTime position); 118 | void aur_client_get_volume (AurClient * client, gdouble volume); 119 | void aur_client_set_volume (AurClient * client, gdouble volume); 120 | const GArray *aur_client_get_player_info (AurClient * client); 121 | gboolean aur_client_get_player_enabled (AurClient * client, guint id); 122 | void aur_client_set_player_enabled (AurClient * client, guint id, gboolean enabled); 123 | void aur_client_set_player_volume (AurClient * client, guint id, gdouble volume); 124 | void aur_client_set_language (AurClient * client, const gchar *language_code); 125 | 126 | G_END_DECLS 127 | #endif 128 | -------------------------------------------------------------------------------- /src/common/aur-json.c: -------------------------------------------------------------------------------- 1 | /* GStreamer 2 | * Copyright (C) 2012-2014 Jan Schmidt 3 | * 4 | * This library is free software; you can redistribute it and/or 5 | * modify it under the terms of the GNU Library General Public 6 | * License as published by the Free Software Foundation; either 7 | * version 2 of the License, or (at your option) any later version. 8 | * 9 | * This library is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 12 | * Library General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU Library General Public 15 | * License along with this library; if not, write to the 16 | * Free Software Foundation, Inc., 59 Temple Place - Suite 330, 17 | * Boston, MA 02111-1307, USA. 18 | */ 19 | 20 | #ifdef HAVE_CONFIG_H 21 | #include "config.h" 22 | #endif 23 | 24 | #include 25 | #include 26 | 27 | #include 28 | 29 | static void 30 | aur_json_array_add_to_val (JsonArray *array, guint index_, 31 | JsonNode *element_node, GValue *outval); 32 | 33 | static void 34 | aur_json_node_into_val (JsonNode *element_node, GValue *v) 35 | { 36 | if (JSON_NODE_HOLDS_OBJECT (element_node)) { 37 | GstStructure *child = aur_json_to_gst_structure (element_node); 38 | g_value_init (v, GST_TYPE_STRUCTURE); 39 | gst_value_set_structure (v, child); 40 | } 41 | else if (JSON_NODE_HOLDS_ARRAY (element_node)) { 42 | JsonArray *arr = json_node_get_array (element_node); 43 | g_value_init (v, GST_TYPE_ARRAY); 44 | json_array_foreach_element (arr, 45 | (JsonArrayForeach) aur_json_array_add_to_val, v); 46 | } 47 | else { 48 | json_node_get_value (element_node, v); 49 | } 50 | } 51 | 52 | static void 53 | aur_json_array_add_to_val (G_GNUC_UNUSED JsonArray *array, 54 | G_GNUC_UNUSED guint index_, 55 | JsonNode *element_node, GValue *outval) 56 | { 57 | GValue v = G_VALUE_INIT; 58 | aur_json_node_into_val (element_node, &v); 59 | gst_value_array_append_value (outval, &v); 60 | g_value_unset (&v); 61 | } 62 | 63 | static void 64 | aur_gst_struct_from_object (G_GNUC_UNUSED JsonObject * o, 65 | const gchar * member_name, JsonNode * member_node, GstStructure * s) 66 | { 67 | GValue v = G_VALUE_INIT; 68 | aur_json_node_into_val (member_node, &v); 69 | gst_structure_set_value (s, member_name, &v); 70 | g_value_unset (&v); 71 | } 72 | 73 | GstStructure * 74 | aur_json_to_gst_structure (JsonNode * root) 75 | { 76 | GstStructure *s = NULL; 77 | JsonObject *o; 78 | 79 | if (!JSON_NODE_HOLDS_OBJECT (root)) 80 | return NULL; 81 | 82 | s = gst_structure_new ("json", NULL, NULL); 83 | 84 | o = json_node_get_object (root); 85 | json_object_foreach_member (o, 86 | (JsonObjectForeach) aur_gst_struct_from_object, s); 87 | 88 | return s; 89 | } 90 | 91 | static JsonNode * 92 | aur_json_value_to_node (const GValue *value) 93 | { 94 | JsonNode *n = NULL; 95 | 96 | if (GST_VALUE_HOLDS_STRUCTURE (value)) { 97 | const GstStructure *s = gst_value_get_structure (value); 98 | n = aur_json_from_gst_structure (s); 99 | } 100 | else if (GST_VALUE_HOLDS_ARRAY (value)) { 101 | guint count = gst_value_array_get_size (value); 102 | guint i; 103 | JsonArray *arr = json_array_sized_new (count); 104 | for (i = 0; i < count; i++) { 105 | const GValue *sub_val = gst_value_array_get_value (value, i); 106 | JsonNode *tmp = aur_json_value_to_node (sub_val); 107 | if (tmp) 108 | json_array_add_element (arr, tmp); 109 | } 110 | n = json_node_new (JSON_NODE_ARRAY); 111 | json_node_take_array (n, arr); 112 | } else if (G_VALUE_HOLDS_UINT64 (value)) { 113 | gint64 tmp = (gint64)(g_value_get_uint64 (value)); 114 | /* Can't represent things > 2^63-1 */ 115 | if (tmp < 0) 116 | return NULL; 117 | n = json_node_new (JSON_NODE_VALUE); 118 | json_node_set_int (n, tmp); 119 | } else { 120 | n = json_node_new (JSON_NODE_VALUE); 121 | json_node_set_value (n, value); 122 | } 123 | 124 | return n; 125 | } 126 | 127 | static gboolean 128 | aur_add_struct_object (GQuark field_id, const GValue * value, JsonObject * o) 129 | { 130 | JsonNode *n = aur_json_value_to_node (value); 131 | if (n) 132 | json_object_set_member (o, g_quark_to_string (field_id), n); 133 | return TRUE; 134 | } 135 | 136 | JsonNode * 137 | aur_json_from_gst_structure (const GstStructure * s) 138 | { 139 | JsonNode *root = json_node_new (JSON_NODE_OBJECT); 140 | 141 | json_node_take_object (root, json_object_new ()); 142 | 143 | gst_structure_foreach (s, 144 | (GstStructureForeachFunc) aur_add_struct_object, 145 | json_node_get_object (root)); 146 | 147 | return root; 148 | } 149 | 150 | static gboolean 151 | aur_json_structure_get_as (const GstStructure * structure, 152 | const gchar * fieldname, GType t, GValue * dest) 153 | { 154 | const GValue *v1 = gst_structure_get_value (structure, fieldname); 155 | if (v1 == NULL) 156 | return FALSE; 157 | 158 | g_value_init (dest, t); 159 | g_value_transform (v1, dest); 160 | 161 | return TRUE; 162 | } 163 | 164 | gboolean 165 | aur_json_structure_get_int (const GstStructure * structure, 166 | const gchar * fieldname, gint * value) 167 | { 168 | GValue dest = G_VALUE_INIT; 169 | gboolean res = 170 | aur_json_structure_get_as (structure, fieldname, G_TYPE_INT, &dest); 171 | 172 | if (res) { 173 | if (value) 174 | *value = g_value_get_int (&dest); 175 | g_value_unset (&dest); 176 | } 177 | return TRUE; 178 | } 179 | 180 | gboolean 181 | aur_json_structure_get_int64 (const GstStructure * structure, 182 | const gchar * fieldname, gint64 * value) 183 | { 184 | GValue dest = G_VALUE_INIT; 185 | gboolean res = 186 | aur_json_structure_get_as (structure, fieldname, G_TYPE_INT64, &dest); 187 | 188 | if (res) { 189 | if (value) 190 | *value = g_value_get_int64 (&dest); 191 | g_value_unset (&dest); 192 | } 193 | return res; 194 | } 195 | 196 | gboolean 197 | aur_json_structure_get_double (const GstStructure * structure, 198 | const gchar * fieldname, gdouble * value) 199 | { 200 | GValue dest = G_VALUE_INIT; 201 | gboolean res = 202 | aur_json_structure_get_as (structure, fieldname, G_TYPE_DOUBLE, &dest); 203 | 204 | if (res) { 205 | if (value) 206 | *value = g_value_get_double (&dest); 207 | g_value_unset (&dest); 208 | } 209 | return res; 210 | } 211 | 212 | gboolean 213 | aur_json_structure_get_boolean (const GstStructure * structure, 214 | const gchar * fieldname, gboolean * value) 215 | { 216 | GValue dest = G_VALUE_INIT; 217 | gboolean res = aur_json_structure_get_as (structure, fieldname, 218 | G_TYPE_BOOLEAN, &dest); 219 | 220 | if (res) { 221 | if (value) 222 | *value = g_value_get_boolean (&dest); 223 | g_value_unset (&dest); 224 | } 225 | return res; 226 | } 227 | -------------------------------------------------------------------------------- /src/common/aur-json.h: -------------------------------------------------------------------------------- 1 | /* GStreamer 2 | * Copyright (C) 2012-2014 Jan Schmidt 3 | * 4 | * This library is free software; you can redistribute it and/or 5 | * modify it under the terms of the GNU Library General Public 6 | * License as published by the Free Software Foundation; either 7 | * version 2 of the License, or (at your option) any later version. 8 | * 9 | * This library is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 12 | * Library General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU Library General Public 15 | * License along with this library; if not, write to the 16 | * Free Software Foundation, Inc., 59 Temple Place - Suite 330, 17 | * Boston, MA 02111-1307, USA. 18 | */ 19 | 20 | #ifndef __AUR_JSON_H__ 21 | #define __AUR_JSON_H__ 22 | 23 | #include 24 | #include 25 | 26 | G_BEGIN_DECLS 27 | 28 | GstStructure *aur_json_to_gst_structure (JsonNode *root); 29 | JsonNode *aur_json_from_gst_structure (const GstStructure *s); 30 | gboolean aur_json_structure_get_int (const GstStructure *structure, 31 | const gchar *fieldname, gint *value); 32 | gboolean aur_json_structure_get_int64 (const GstStructure *structure, 33 | const gchar *fieldname, gint64 *value); 34 | gboolean aur_json_structure_get_double (const GstStructure *structure, 35 | const gchar *fieldname, gdouble *value); 36 | gboolean aur_json_structure_get_boolean (const GstStructure *structure, 37 | const gchar *fieldname, gboolean *value); 38 | 39 | G_END_DECLS 40 | 41 | #endif 42 | -------------------------------------------------------------------------------- /src/common/aur-types.h: -------------------------------------------------------------------------------- 1 | /* GStreamer 2 | * Copyright (C) 2012-2014 Jan Schmidt 3 | * 4 | * This library is free software; you can redistribute it and/or 5 | * modify it under the terms of the GNU Library General Public 6 | * License as published by the Free Software Foundation; either 7 | * version 2 of the License, or (at your option) any later version. 8 | * 9 | * This library is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 12 | * Library General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU Library General Public 15 | * License along with this library; if not, write to the 16 | * Free Software Foundation, Inc., 59 Temple Place - Suite 330, 17 | * Boston, MA 02111-1307, USA. 18 | */ 19 | #ifndef __AUR_TYPES_H__ 20 | #define __AUR_TYPES_H__ 21 | 22 | #include 23 | 24 | G_BEGIN_DECLS 25 | 26 | typedef struct _AurAvahi AurAvahi; 27 | typedef struct _AurClient AurClient; 28 | typedef struct _AurConfig AurConfig; 29 | typedef struct _AurHttpResource AurHttpResource; 30 | typedef struct _AurManager AurManager; 31 | typedef struct _AurMediaDB AurMediaDB; 32 | typedef struct _AurServer AurServer; 33 | typedef struct _AurServerClient AurServerClient; 34 | 35 | G_END_DECLS 36 | 37 | #endif 38 | -------------------------------------------------------------------------------- /src/common/aur-websocket-parser.c: -------------------------------------------------------------------------------- 1 | /* GStreamer 2 | * Copyright (C) 2012-2015 Jan Schmidt 3 | * 4 | * This library is free software; you can redistribute it and/or 5 | * modify it under the terms of the GNU Library General Public 6 | * License as published by the Free Software Foundation; either 7 | * version 2 of the License, or (at your option) any later version. 8 | * 9 | * This library is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 12 | * Library General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU Library General Public 15 | * License along with this library; if not, write to the 16 | * Free Software Foundation, Inc., 59 Temple Place - Suite 330, 17 | * Boston, MA 02111-1307, USA. 18 | */ 19 | 20 | #ifdef HAVE_CONFIG_H 21 | #include "config.h" 22 | #endif 23 | 24 | #include 25 | #include 26 | 27 | #include "aur-websocket-parser.h" 28 | 29 | enum 30 | { 31 | MSG_RECEIVED, 32 | LAST_SIGNAL 33 | }; 34 | 35 | G_DEFINE_TYPE (AurWebSocketParser, aur_websocket_parser, G_TYPE_OBJECT); 36 | 37 | static guint aur_websocket_parser_signals[LAST_SIGNAL] = { 0 }; 38 | 39 | static void aur_websocket_parser_finalize (GObject * object); 40 | 41 | AurWebSocketParser * 42 | aur_websocket_parser_new () 43 | { 44 | return g_object_new (AUR_TYPE_WEBSOCKET_PARSER, NULL); 45 | } 46 | 47 | static void 48 | aur_websocket_parser_init (AurWebSocketParser * parser) 49 | { 50 | parser->in_bufsize = 1024; 51 | parser->in_bufptr = parser->in_buf = g_new0 (gchar, parser->in_bufsize); 52 | parser->in_bufavail = 0; 53 | } 54 | 55 | static void 56 | aur_websocket_parser_class_init (AurWebSocketParserClass * parser_class) 57 | { 58 | GObjectClass *gobject_class = (GObjectClass *) (parser_class); 59 | 60 | gobject_class->finalize = aur_websocket_parser_finalize; 61 | 62 | aur_websocket_parser_signals[MSG_RECEIVED] = 63 | g_signal_new ("message-received", G_TYPE_FROM_CLASS (parser_class), 64 | G_SIGNAL_RUN_LAST, G_STRUCT_OFFSET (AurWebSocketParserClass, message_received), NULL, NULL, 65 | g_cclosure_marshal_generic, G_TYPE_NONE, 2, G_TYPE_CHAR, G_TYPE_UINT64); 66 | } 67 | 68 | static void 69 | aur_websocket_parser_finalize (GObject * object) 70 | { 71 | AurWebSocketParser *parser = (AurWebSocketParser *) (object); 72 | 73 | g_free (parser->in_buf); 74 | 75 | G_OBJECT_CLASS (aur_websocket_parser_parent_class)->finalize (object); 76 | } 77 | 78 | static gboolean 79 | try_parse_websocket_fragment (AurWebSocketParser * parser) 80 | { 81 | guint64 frag_size; 82 | gchar *header, *outptr; 83 | gchar *mask; 84 | gchar *decoded; 85 | gsize i, avail; 86 | GIOStatus status = G_IO_STATUS_NORMAL; 87 | 88 | //g_print ("Got %u bytes to parse\n", (guint) parser->in_bufavail); 89 | if (parser->in_bufavail < 2) 90 | return G_IO_STATUS_AGAIN; 91 | 92 | header = outptr = parser->in_bufptr; 93 | avail = parser->in_bufavail; 94 | 95 | #if 0 96 | if (header[0] & 0x80) 97 | g_print ("FIN flag. Payload type 0x%x\n", header[0] & 0xf); 98 | #endif 99 | 100 | frag_size = header[1] & 0x7f; 101 | outptr += 2; 102 | 103 | if (frag_size < 126) { 104 | avail -= 2; 105 | } else if (frag_size == 126) { 106 | if (avail < 4) 107 | return FALSE; 108 | frag_size = GST_READ_UINT16_BE (outptr); 109 | outptr += 2; 110 | avail -= 8; 111 | } else { 112 | if (avail < 10) 113 | return FALSE; 114 | frag_size = GST_READ_UINT64_BE (outptr); 115 | outptr += 8; 116 | avail -= 8; 117 | } 118 | 119 | if ((header[1] & 0x80) == 0) { 120 | g_warning ("Received packet not masked. Dropping connection"); 121 | status = G_IO_STATUS_ERROR; 122 | goto skip_out; 123 | } 124 | 125 | if (avail < 4 + frag_size) { 126 | /* Wait for more data */ 127 | return G_IO_STATUS_AGAIN; 128 | } 129 | 130 | /* Consume the 4 mask bytes */ 131 | mask = outptr; 132 | outptr += 4; 133 | avail -= 4; 134 | 135 | decoded = g_malloc (frag_size + 1); 136 | for (i = 0; i < frag_size; i++) { 137 | decoded[i] = outptr[i] ^ mask[i % 4]; 138 | } 139 | decoded[frag_size] = 0; 140 | 141 | /* Fire a signal to get this packet processed */ 142 | #if 0 143 | g_print ("Have websocket msg of size %" G_GUINT64_FORMAT "\n", 144 | frag_size); 145 | #endif 146 | g_signal_emit (parser, aur_websocket_parser_signals[MSG_RECEIVED], 0, 147 | decoded, (guint64) (frag_size)); 148 | 149 | g_free (decoded); 150 | 151 | skip_out: 152 | { 153 | gsize consumed; 154 | 155 | outptr += frag_size; 156 | 157 | consumed = outptr - parser->in_bufptr; 158 | 159 | parser->in_bufavail -= consumed; 160 | parser->in_bufptr = outptr; 161 | } 162 | 163 | return status; 164 | } 165 | 166 | GIOStatus 167 | aur_websocket_parser_read_io (AurWebSocketParser * p, GIOChannel * io) 168 | { 169 | GIOStatus status; 170 | gsize bread = 0; 171 | 172 | if (p->in_bufsize <= p->in_bufavail) { 173 | gsize cur_offs = p->in_bufptr - p->in_buf; 174 | 175 | p->in_bufsize *= 2; 176 | g_print ("Growing io_buf to %" G_GSIZE_FORMAT " bytes\n", p->in_bufsize); 177 | 178 | p->in_buf = g_renew (gchar, p->in_buf, p->in_bufsize); 179 | p->in_bufptr = p->in_buf + cur_offs; 180 | } 181 | 182 | do { 183 | status = 184 | g_io_channel_read_chars (io, 185 | p->in_buf + p->in_bufavail, p->in_bufsize - p->in_bufavail, &bread, NULL); 186 | } while (status == G_IO_STATUS_AGAIN); 187 | 188 | if (status != G_IO_STATUS_NORMAL) 189 | return status; 190 | 191 | p->in_bufavail += bread; 192 | 193 | while (p->in_bufavail > 0 && status == G_IO_STATUS_NORMAL) 194 | status = try_parse_websocket_fragment (p); 195 | 196 | if (status == G_IO_STATUS_AGAIN) 197 | status = G_IO_STATUS_NORMAL; 198 | 199 | if (p->in_buf != p->in_bufptr) { 200 | memmove (p->in_buf, p->in_bufptr, p->in_bufavail); 201 | p->in_bufptr = p->in_buf; 202 | } 203 | 204 | return status; 205 | } 206 | -------------------------------------------------------------------------------- /src/common/aur-websocket-parser.h: -------------------------------------------------------------------------------- 1 | /* GStreamer 2 | * Copyright (C) 2012-2015 Jan Schmidt 3 | * 4 | * This library is free software; you can redistribute it and/or 5 | * modify it under the terms of the GNU Library General Public 6 | * License as published by the Free Software Foundation; either 7 | * version 2 of the License, or (at your option) any later version. 8 | * 9 | * This library is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 12 | * Library General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU Library General Public 15 | * License along with this library; if not, write to the 16 | * Free Software Foundation, Inc., 59 Temple Place - Suite 330, 17 | * Boston, MA 02111-1307, USA. 18 | */ 19 | 20 | #ifndef __AUR_WEBSOCKET_PARSER_H__ 21 | #define __AUR_WEBSOCKET_PARSER_H__ 22 | 23 | #include 24 | 25 | #include 26 | 27 | G_BEGIN_DECLS 28 | 29 | #define AUR_TYPE_WEBSOCKET_PARSER (aur_websocket_parser_get_type ()) 30 | #define AUR_WEBSOCKET_PARSER(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj),AUR_TYPE_WEBSOCKET_PARSER, AurWebSocketParser)) 31 | #define AUR_WEBSOCKET_PARSER_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST((klass),AUR_TYPE_WEBSOCKET_PARSER, AurWebSocketParser)) 32 | #define AUR_IS_WEBSOCKET_PARSER(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj),AUR_TYPE_WEBSOCKET_PARSER)) 33 | #define AUR_IS_WEBSOCKET_PARSER_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE((klass),AUR_TYPE_WEBSOCKET_PARSER)) 34 | #define AUR_WEBSOCKET_PARSER_CAST(obj) ((AurWebSocketParser*)(obj)) 35 | 36 | GType aur_websocket_parser_get_type(void); 37 | 38 | typedef struct _AurWebSocketParser AurWebSocketParser; 39 | typedef struct _AurWebSocketParserClass AurWebSocketParserClass; 40 | typedef enum _AurWebSocketParserResult AurWebSocketParserResult; 41 | 42 | enum _AurWebSocketParserResult { 43 | AUR_WEBSOCKET_PARSER_OK, 44 | AUR_WEBSOCKET_PARSER_MORE_DATA, 45 | AUR_WEBSOCKET_PARSER_ERROR 46 | }; 47 | 48 | struct _AurWebSocketParser 49 | { 50 | GObject parent; 51 | 52 | gchar *in_buf; 53 | gchar *in_bufptr; 54 | gsize in_bufsize; 55 | gsize in_bufavail; 56 | }; 57 | 58 | struct _AurWebSocketParserClass 59 | { 60 | GObjectClass parent; 61 | 62 | void (*message_received) (AurWebSocketParser *parser, gchar *data, guint64 len); 63 | }; 64 | 65 | AurWebSocketParser *aur_websocket_parser_new (); 66 | GIOStatus aur_websocket_parser_read_io (AurWebSocketParser *p, GIOChannel *io); 67 | #endif 68 | -------------------------------------------------------------------------------- /src/gnome-client.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # Copyright Ian McKellar 2012 (adapted from linux-rdio) 3 | 4 | from gi.repository import GLib, Gio, GdkPixbuf, Gtk, Soup, JSCore, WebKit 5 | import os 6 | 7 | class AurenaView(WebKit.WebView): 8 | def __init__(self): 9 | WebKit.WebView.__init__(self) 10 | 11 | # scale other content besides from text as well 12 | self.set_full_content_zoom(True) 13 | 14 | self.load_uri('http://localhost:5457/ui/') 15 | 16 | class AurenaWindow(Gtk.Window): 17 | def __init__(self): 18 | Gtk.Window.__init__(self) 19 | self.set_default_size(700, 350) 20 | self.set_title('Aurena') 21 | 22 | self.web_view = AurenaView() 23 | 24 | self.scrolled_window = Gtk.ScrolledWindow() 25 | self.scrolled_window.add(self.web_view) 26 | self.scrolled_window.show_all() 27 | 28 | self.add(self.scrolled_window) 29 | 30 | self.web_view.connect('title-changed', self.title_changed) 31 | 32 | # TODO: async? 33 | icon_file = Gio.File.new_for_uri('http://localhost:5457/ui/icon.png') 34 | icon = GdkPixbuf.Pixbuf.new_from_stream(icon_file.read(None), None) 35 | self.set_icon(icon) 36 | 37 | # Set up for media key handling 38 | bus = Gio.bus_get_sync(Gio.BusType.SESSION, None) 39 | self.proxy = Gio.DBusProxy.new_sync(bus, 0, None, 'org.gnome.SettingsDaemon', 40 | '/org/gnome/SettingsDaemon/MediaKeys', 'org.gnome.SettingsDaemon.MediaKeys', None) 41 | self.proxy.call_sync('GrabMediaPlayerKeys', GLib.Variant('(su)', ('Aurena', 0)), 0, -1, None) 42 | self.proxy.connect('g-signal', self.mediakey_signal) 43 | 44 | def mediakey_signal(self, proxy, sender, signal, parameters): 45 | if signal != 'MediaPlayerKeyPressed': 46 | return 47 | key = parameters.get_child_value(1).get_string() 48 | if key == 'Play': 49 | self.web_view.execute_script('aurena.playPause()') 50 | if key == 'Next': 51 | self.web_view.execute_script('aurena.next()') 52 | if key == 'Previous': 53 | self.web_view.execute_script('aurena.previous()') 54 | 55 | def title_changed(self, view, frame, title): 56 | if frame == view.get_main_frame(): 57 | self.set_title(title) 58 | 59 | # make a data dir for this app 60 | data_dir = os.path.join(GLib.get_user_data_dir(), 'aurena') 61 | if not os.path.exists(data_dir): 62 | os.makedirs(data_dir) 63 | 64 | # put cookies in it 65 | cookie_jar = os.path.join(data_dir, 'cookies.txt') 66 | jar = Soup.CookieJarText(filename=cookie_jar) 67 | session = WebKit.get_default_session() 68 | session.add_feature(jar) 69 | 70 | w = AurenaWindow() 71 | w.show() 72 | w.connect('destroy', lambda w: Gtk.main_quit()) 73 | 74 | Gtk.main() 75 | -------------------------------------------------------------------------------- /src/icon.xcf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thaytan/aurena/ccc0530a57edf8e86729abaf378779d2e689aabf/src/icon.xcf -------------------------------------------------------------------------------- /src/server/Makefile.am: -------------------------------------------------------------------------------- 1 | noinst_LTLIBRARIES = libaurena_server.la 2 | 3 | libaurena_server_la_SOURCES = \ 4 | aur-avahi.c \ 5 | aur-avahi.h \ 6 | aur-config.c \ 7 | aur-config.h \ 8 | aur-http-resource.c \ 9 | aur-http-resource.h \ 10 | aur-manager.c \ 11 | aur-manager.h \ 12 | aur-media-db.c \ 13 | aur-media-db.h \ 14 | aur-resource.c \ 15 | aur-resource.h \ 16 | aur-server.c \ 17 | aur-server.h \ 18 | aur-server-client.c \ 19 | aur-server-client.h 20 | 21 | libaurena_server_la_CPPFLAGS = -I$(top_srcdir) $(AUR_COMMON_CFLAGS) $(GST_CFLAGS) $(GST_RTSP_CFLAGS) $(EXTRA_CFLAGS) 22 | libaurena_server_la_LIBADD = 23 | -------------------------------------------------------------------------------- /src/server/aur-avahi.c: -------------------------------------------------------------------------------- 1 | /* GStreamer 2 | * Copyright (C) 2012-2014 Jan Schmidt 3 | * 4 | * This library is free software; you can redistribute it and/or 5 | * modify it under the terms of the GNU Library General Public 6 | * License as published by the Free Software Foundation; either 7 | * version 2 of the License, or (at your option) any later version. 8 | * 9 | * This library is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 12 | * Library General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU Library General Public 15 | * License along with this library; if not, write to the 16 | * Free Software Foundation, Inc., 59 Temple Place - Suite 330, 17 | * Boston, MA 02111-1307, USA. 18 | */ 19 | 20 | #ifdef HAVE_CONFIG_H 21 | #include "config.h" 22 | #endif 23 | 24 | #include 25 | #include 26 | 27 | #include 28 | #include 29 | 30 | #include 31 | #include 32 | 33 | #include "aur-avahi.h" 34 | 35 | G_DEFINE_TYPE (AurAvahi, aur_avahi, G_TYPE_OBJECT); 36 | 37 | enum 38 | { 39 | PROP_0, 40 | PROP_PORT, 41 | PROP_LAST 42 | }; 43 | 44 | static void aur_avahi_constructed (GObject * object); 45 | static void aur_avahi_set_property (GObject * object, guint prop_id, 46 | const GValue * value, GParamSpec * pspec); 47 | static void aur_avahi_get_property (GObject * object, guint prop_id, 48 | GValue * value, GParamSpec * pspec); 49 | 50 | struct _AurAvahiPrivate 51 | { 52 | const AvahiPoll *poll_api; 53 | AvahiGLibPoll *glib_poll; 54 | AvahiClient *client; 55 | AvahiEntryGroup *group; 56 | char *service_name; 57 | int port; 58 | }; 59 | 60 | static void aur_avahi_finalize (GObject * object); 61 | 62 | static void create_service (AurAvahi * avahi); 63 | 64 | static void 65 | entry_group_callback (AVAHI_GCC_UNUSED AvahiEntryGroup * g, AvahiEntryGroupState state, 66 | AVAHI_GCC_UNUSED void *userdata) 67 | { 68 | AurAvahi *avahi = (AurAvahi *) (userdata); 69 | AurAvahiPrivate *priv = avahi->priv; 70 | 71 | switch (state) { 72 | case AVAHI_ENTRY_GROUP_ESTABLISHED: 73 | /* The entry group has been established successfully */ 74 | g_print ("Service '%s' successfully established.\n", priv->service_name); 75 | break; 76 | case AVAHI_ENTRY_GROUP_COLLISION:{ 77 | char *n; 78 | 79 | /* A service name collision with a remote service 80 | * happened. Let's pick a new name */ 81 | n = avahi_alternative_service_name (priv->service_name); 82 | avahi_free (priv->service_name); 83 | priv->service_name = n; 84 | 85 | g_message ("Service name collision, renaming service to '%s'", 86 | priv->service_name); 87 | 88 | /* And recreate the services */ 89 | create_service (avahi); 90 | break; 91 | } 92 | case AVAHI_ENTRY_GROUP_FAILURE: 93 | g_warning("Entry group failure: %s", 94 | avahi_strerror (avahi_client_errno (priv->client))); 95 | break; 96 | default: 97 | break; 98 | } 99 | } 100 | 101 | static void 102 | create_service (AurAvahi * avahi) 103 | { 104 | AurAvahiPrivate *priv = avahi->priv; 105 | int ret; 106 | 107 | do { 108 | if (priv->group == NULL) { 109 | priv->group = 110 | avahi_entry_group_new (priv->client, entry_group_callback, avahi); 111 | if (priv->group == NULL) { 112 | g_critical ("avahi_entry_group_new() failed: %s\n", 113 | avahi_strerror (avahi_client_errno (priv->client))); 114 | goto fail; 115 | } 116 | } 117 | 118 | /* If the group is empty (either because it was just created, or 119 | * because it was reset previously, add our entries. */ 120 | if (avahi_entry_group_is_empty (priv->group)) { 121 | g_message ("Adding service '%s' on port %d", priv->service_name, priv->port); 122 | 123 | ret = 124 | avahi_entry_group_add_service (priv->group, AVAHI_IF_UNSPEC, 125 | AVAHI_PROTO_UNSPEC, 0, priv->service_name, "_aurena._tcp", NULL, 126 | NULL, priv->port, NULL); 127 | if (ret < 0) { 128 | if (ret == AVAHI_ERR_COLLISION) { 129 | /* A service name collision with a local service happened. Let's 130 | * pick a new name */ 131 | char *n = avahi_alternative_service_name (priv->service_name); 132 | g_free (priv->service_name); 133 | priv->service_name = n; 134 | g_message ("Service name collision, renaming service to '%s'\n", 135 | priv->service_name); 136 | avahi_entry_group_reset (priv->group); 137 | continue; 138 | } else { 139 | g_critical ("Failed to add _aurena._tcp service: %s", 140 | avahi_strerror (ret)); 141 | goto fail; 142 | } 143 | } 144 | #if 0 145 | /* Add an additional (hypothetic) subtype */ 146 | if ((ret = 147 | avahi_entry_group_add_service_subtype (group, AVAHI_IF_UNSPEC, 148 | AVAHI_PROTO_UNSPEC, 0, name, "_printer._tcp", NULL, 149 | "_magic._sub._printer._tcp") < 0)) { 150 | fprintf (stderr, 151 | "Failed to add subtype _magic._sub._printer._tcp: %s\n", 152 | avahi_strerror (ret)); 153 | goto fail; 154 | } 155 | #endif 156 | 157 | if ((ret = avahi_entry_group_commit (priv->group)) < 0) { 158 | g_critical ("Failed to commit entry group: %s\n", avahi_strerror (ret)); 159 | } 160 | } 161 | return; 162 | } while (TRUE); 163 | 164 | fail: 165 | return; 166 | } 167 | 168 | static void 169 | avahi_client_callback (AVAHI_GCC_UNUSED AvahiClient * client, 170 | AvahiClientState state, void *userdata) 171 | { 172 | AurAvahi *avahi = (AurAvahi *) (userdata); 173 | 174 | switch (state) { 175 | case AVAHI_CLIENT_S_RUNNING: 176 | /* Create services now */ 177 | avahi->priv->client = client; 178 | create_service (avahi); 179 | break; 180 | case AVAHI_CLIENT_FAILURE: 181 | g_error ("Client failure: %s\n", 182 | avahi_strerror (avahi_client_errno (client))); 183 | break; 184 | case AVAHI_CLIENT_S_COLLISION: 185 | /* Let's drop our registered services. When the server is back 186 | * in AVAHI_SERVER_RUNNING state we will register them 187 | * again with the new host name. */ 188 | case AVAHI_CLIENT_S_REGISTERING: 189 | /* The server records are now being established. This 190 | * might be caused by a host name change. We need to wait 191 | * to records to register until the host name is 192 | * properly established - so clear out any pending 193 | * registration group. */ 194 | if (avahi->priv->group) 195 | avahi_entry_group_reset (avahi->priv->group); 196 | break; 197 | default: 198 | break; 199 | } 200 | } 201 | 202 | static void 203 | aur_avahi_set_property (GObject * object, guint prop_id, 204 | const GValue * value, GParamSpec * pspec) 205 | { 206 | AurAvahi *avahi = (AurAvahi *) (object); 207 | 208 | switch (prop_id) { 209 | case PROP_PORT: 210 | avahi->priv->port = g_value_get_int (value); 211 | break; 212 | default: 213 | G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); 214 | break; 215 | } 216 | } 217 | 218 | static void 219 | aur_avahi_get_property (GObject * object, guint prop_id, 220 | GValue * value, GParamSpec * pspec) 221 | { 222 | AurAvahi *avahi = (AurAvahi *) (object); 223 | 224 | switch (prop_id) { 225 | case PROP_PORT: 226 | g_value_set_int (value, avahi->priv->port); 227 | break; 228 | default: 229 | G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); 230 | break; 231 | } 232 | } 233 | 234 | 235 | static void 236 | aur_avahi_finalize (GObject * object) 237 | { 238 | AurAvahi *avahi = (AurAvahi *) (object); 239 | 240 | if (avahi->priv->group) 241 | avahi_entry_group_free (avahi->priv->group); 242 | if (avahi->priv->client) 243 | avahi_client_free (avahi->priv->client); 244 | 245 | avahi_glib_poll_free (avahi->priv->glib_poll); 246 | } 247 | 248 | static void 249 | aur_avahi_init (AurAvahi * avahi) 250 | { 251 | AurAvahiPrivate *priv = avahi->priv = 252 | G_TYPE_INSTANCE_GET_PRIVATE (avahi, AUR_TYPE_AVAHI, AurAvahiPrivate); 253 | 254 | priv->service_name = g_strdup ("Aurena media server"); 255 | 256 | priv->glib_poll = avahi_glib_poll_new (NULL, G_PRIORITY_DEFAULT); 257 | priv->poll_api = avahi_glib_poll_get (priv->glib_poll); 258 | priv->port = 5457; 259 | } 260 | 261 | static void 262 | aur_avahi_class_init (AurAvahiClass * klass) 263 | { 264 | GObjectClass *gobject_class = (GObjectClass *) (klass); 265 | 266 | gobject_class->constructed = aur_avahi_constructed; 267 | gobject_class->finalize = aur_avahi_finalize; 268 | gobject_class->set_property = aur_avahi_set_property; 269 | gobject_class->get_property = aur_avahi_get_property; 270 | 271 | g_type_class_add_private (gobject_class, sizeof (AurAvahiPrivate)); 272 | 273 | g_object_class_install_property (gobject_class, PROP_PORT, 274 | g_param_spec_int ("aur-port", "Aurena port", 275 | "port for Aurena service", 276 | 1, 65535, 5457, 277 | G_PARAM_READWRITE|G_PARAM_CONSTRUCT)); 278 | 279 | avahi_set_allocator (avahi_glib_allocator ()); 280 | } 281 | 282 | static void 283 | aur_avahi_constructed (GObject * object) 284 | { 285 | AurAvahi *avahi = (AurAvahi *) (object); 286 | AurAvahiPrivate *priv = avahi->priv; 287 | int error = 0; 288 | 289 | if (G_OBJECT_CLASS (aur_avahi_parent_class)->constructed != NULL) 290 | G_OBJECT_CLASS (aur_avahi_parent_class)->constructed (object); 291 | 292 | priv->client = 293 | avahi_client_new (priv->poll_api, 0, avahi_client_callback, avahi, 294 | &error); 295 | 296 | /* Check the error return code */ 297 | if (priv->client == NULL) { 298 | /* Print out the error string */ 299 | g_warning ("Error initializing Avahi: %s", avahi_strerror (error)); 300 | } 301 | } 302 | 303 | AurAvahi * 304 | aur_avahi_new (int port) 305 | { 306 | return g_object_new (AUR_TYPE_AVAHI, "aur-port", port, NULL); 307 | } 308 | -------------------------------------------------------------------------------- /src/server/aur-avahi.h: -------------------------------------------------------------------------------- 1 | /* GStreamer 2 | * Copyright (C) 2012-2014 Jan Schmidt 3 | * 4 | * This library is free software; you can redistribute it and/or 5 | * modify it under the terms of the GNU Library General Public 6 | * License as published by the Free Software Foundation; either 7 | * version 2 of the License, or (at your option) any later version. 8 | * 9 | * This library is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 12 | * Library General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU Library General Public 15 | * License along with this library; if not, write to the 16 | * Free Software Foundation, Inc., 59 Temple Place - Suite 330, 17 | * Boston, MA 02111-1307, USA. 18 | */ 19 | #ifndef __AUR_AVAHI_H__ 20 | #define __AUR_AVAHI_H__ 21 | 22 | #include 23 | #include 24 | 25 | #include 26 | 27 | G_BEGIN_DECLS 28 | 29 | #define AUR_TYPE_AVAHI (aur_avahi_get_type ()) 30 | 31 | typedef struct _AurAvahiClass AurAvahiClass; 32 | typedef struct _AurAvahiPrivate AurAvahiPrivate; 33 | 34 | struct _AurAvahi 35 | { 36 | GObject parent; 37 | AurAvahiPrivate *priv; 38 | }; 39 | 40 | struct _AurAvahiClass 41 | { 42 | GObjectClass parent; 43 | }; 44 | 45 | GType aur_avahi_get_type(void); 46 | 47 | AurAvahi *aur_avahi_new(int port); 48 | 49 | G_END_DECLS 50 | 51 | #endif 52 | -------------------------------------------------------------------------------- /src/server/aur-config.c: -------------------------------------------------------------------------------- 1 | /* GStreamer 2 | * Copyright (C) 2012-2014 Jan Schmidt 3 | * 4 | * This library is free software; you can redistribute it and/or 5 | * modify it under the terms of the GNU Library General Public 6 | * License as published by the Free Software Foundation; either 7 | * version 2 of the License, or (at your option) any later version. 8 | * 9 | * This library is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 12 | * Library General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU Library General Public 15 | * License along with this library; if not, write to the 16 | * Free Software Foundation, Inc., 59 Temple Place - Suite 330, 17 | * Boston, MA 02111-1307, USA. 18 | */ 19 | 20 | #ifdef HAVE_CONFIG_H 21 | #include "config.h" 22 | #endif 23 | 24 | #include 25 | #include 26 | 27 | #include "aur-config.h" 28 | 29 | G_DEFINE_TYPE (AurConfig, aur_config, G_TYPE_OBJECT); 30 | 31 | enum 32 | { 33 | PROP_0, 34 | PROP_CONFIG_FILE, 35 | PROP_PORT, 36 | PROP_RTSP_PORT, 37 | PROP_DATABASE, 38 | PROP_PLAYLIST, 39 | PROP_LAST 40 | }; 41 | 42 | static void aur_config_set_property (GObject * object, guint prop_id, 43 | const GValue * value, GParamSpec * pspec); 44 | static void aur_config_get_property (GObject * object, guint prop_id, 45 | GValue * value, GParamSpec * pspec); 46 | 47 | static void aur_config_finalize(GObject *object); 48 | static void aur_config_dispose(GObject *object); 49 | 50 | static gchar * 51 | get_default_db_location () 52 | { 53 | return 54 | g_build_filename (g_get_user_data_dir (), "aurena", "aurena.db", NULL); 55 | } 56 | 57 | static gchar * 58 | get_default_playlist_location () 59 | { 60 | return g_build_filename (g_get_user_config_dir (), 61 | "aurena", "playlist.txt", NULL); 62 | } 63 | 64 | static gchar * 65 | get_default_config_location () 66 | { 67 | return g_build_filename (g_get_user_config_dir (), 68 | "aurena", "config.txt", NULL); 69 | } 70 | 71 | static void 72 | aur_config_init (AurConfig *config) 73 | { 74 | config->config_file = get_default_config_location(); 75 | config->aur_port = 5457; 76 | config->rtsp_port = 5458; 77 | config->database_location = get_default_db_location(); 78 | config->playlist_location = get_default_playlist_location(); 79 | } 80 | 81 | static void 82 | try_read_int (GKeyFile *kf, const gchar *group, const gchar *key, gint *dest) 83 | { 84 | GError *error = NULL; 85 | gint tmp = g_key_file_get_integer (kf, group, key, &error); 86 | 87 | if (error) { 88 | g_error_free (error); 89 | return; 90 | } 91 | *dest = tmp; 92 | } 93 | 94 | static void 95 | try_read_string (GKeyFile *kf, const gchar *group, const gchar *key, 96 | gchar **dest) 97 | { 98 | GError *error = NULL; 99 | gchar *tmp = g_key_file_get_string (kf, group, key, &error); 100 | 101 | if (error) { 102 | g_error_free (error); 103 | return; 104 | } 105 | g_free (*dest); 106 | *dest = tmp; 107 | } 108 | 109 | static void 110 | make_abs_path (gchar **dest, gchar *rel) 111 | { 112 | if (!g_path_is_absolute (*dest)) { 113 | /* Make path absolute, relative to the config file */ 114 | gchar *dir = g_path_get_dirname (rel); 115 | gchar *abs_location = 116 | g_build_filename (dir, *dest, NULL); 117 | g_free (dir); 118 | g_free (*dest); 119 | *dest = abs_location; 120 | } 121 | } 122 | 123 | static void 124 | load_config (G_GNUC_UNUSED AurConfig *config) 125 | { 126 | /* Read in config_file and split out pieces to the vars */ 127 | GKeyFile *kf = g_key_file_new(); 128 | if (kf == NULL) 129 | goto fail; 130 | 131 | if (!g_key_file_load_from_file(kf, config->config_file, 132 | G_KEY_FILE_KEEP_COMMENTS, NULL)) 133 | goto fail; 134 | 135 | try_read_int(kf, "server", "port", &config->aur_port); 136 | try_read_int(kf, "server", "rtsp-port", &config->aur_port); 137 | try_read_string(kf, "server", "database", &config->database_location); 138 | try_read_string(kf, "server", "playlist", &config->playlist_location); 139 | make_abs_path(&config->database_location, config->config_file); 140 | make_abs_path(&config->playlist_location, config->config_file); 141 | 142 | g_key_file_free (kf); 143 | return; 144 | 145 | fail: 146 | if (kf) 147 | g_key_file_free (kf); 148 | g_warning ("Failed to read config file %s", config->config_file); 149 | } 150 | 151 | static void 152 | aur_config_constructed (GObject *object) 153 | { 154 | AurConfig *config = (AurConfig *)(object); 155 | 156 | if (G_OBJECT_CLASS (aur_config_parent_class)->constructed != NULL) 157 | G_OBJECT_CLASS (aur_config_parent_class)->constructed (object); 158 | 159 | load_config (config); 160 | } 161 | 162 | static void 163 | aur_config_class_init (AurConfigClass *config_class) 164 | { 165 | GObjectClass *gobject_class = (GObjectClass *)(config_class); 166 | gchar *location; 167 | 168 | gobject_class->constructed = aur_config_constructed; 169 | 170 | gobject_class->dispose = aur_config_dispose; 171 | gobject_class->finalize = aur_config_finalize; 172 | gobject_class->set_property = aur_config_set_property; 173 | gobject_class->get_property = aur_config_get_property; 174 | 175 | location = get_default_config_location(); 176 | g_object_class_install_property (gobject_class, PROP_CONFIG_FILE, 177 | g_param_spec_string ("config-file", "configuration file", 178 | "Location of the configuration file", 179 | location, G_PARAM_READWRITE|G_PARAM_CONSTRUCT)); 180 | g_free(location); 181 | 182 | g_object_class_install_property (gobject_class, PROP_PORT, 183 | g_param_spec_int ("aur-port", "Aurena port", 184 | "port for Aurena service", 185 | 1, 65535, 5457, 186 | G_PARAM_READWRITE)); 187 | g_object_class_install_property (gobject_class, PROP_RTSP_PORT, 188 | g_param_spec_int ("rtsp-port", "RTSP port", 189 | "port for RTSP server", 190 | 1, 65535, 5458, 191 | G_PARAM_READWRITE)); 192 | 193 | location = get_default_db_location(); 194 | g_object_class_install_property (gobject_class, PROP_DATABASE, 195 | g_param_spec_string ("database", "database", 196 | "Location of the media database file", 197 | location, G_PARAM_READWRITE)); 198 | g_free(location); 199 | 200 | location = get_default_playlist_location(); 201 | g_object_class_install_property (gobject_class, PROP_PLAYLIST, 202 | g_param_spec_string ("playlist", "playlist", 203 | "Location of the media playlist file", 204 | location, G_PARAM_READWRITE)); 205 | g_free(location); 206 | 207 | } 208 | 209 | static void 210 | aur_config_finalize(GObject *object) 211 | { 212 | AurConfig *config = (AurConfig *)(object); 213 | 214 | g_free (config->config_file); 215 | g_free (config->database_location); 216 | g_free (config->playlist_location); 217 | 218 | G_OBJECT_CLASS (aur_config_parent_class)->finalize (object); 219 | } 220 | 221 | static void 222 | aur_config_dispose(GObject *object) 223 | { 224 | G_OBJECT_CLASS (aur_config_parent_class)->dispose (object); 225 | } 226 | 227 | static void 228 | aur_config_set_property (GObject * object, guint prop_id, 229 | const GValue * value, GParamSpec * pspec) 230 | { 231 | AurConfig *config = (AurConfig *)(object); 232 | 233 | switch (prop_id) { 234 | case PROP_CONFIG_FILE: 235 | if (config->config_file) 236 | g_free (config->config_file); 237 | config->config_file = g_value_dup_string (value); 238 | if (config->config_file == NULL) 239 | config->config_file = get_default_config_location(); 240 | break; 241 | case PROP_PORT: 242 | config->aur_port = g_value_get_int (value); 243 | break; 244 | case PROP_RTSP_PORT: 245 | config->rtsp_port = g_value_get_int (value); 246 | break; 247 | case PROP_DATABASE: 248 | if (config->database_location) 249 | g_free (config->database_location); 250 | config->database_location = g_value_dup_string (value); 251 | if (config->database_location == NULL) 252 | config->database_location = get_default_db_location(); 253 | break; 254 | case PROP_PLAYLIST: 255 | if (config->playlist_location) 256 | g_free (config->playlist_location); 257 | config->playlist_location = g_value_dup_string (value); 258 | if (config->playlist_location == NULL) 259 | config->playlist_location = get_default_playlist_location(); 260 | break; 261 | default: 262 | G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); 263 | break; 264 | } 265 | } 266 | 267 | static void 268 | aur_config_get_property (GObject * object, guint prop_id, 269 | GValue * value, GParamSpec * pspec) 270 | { 271 | AurConfig *config = (AurConfig *)(object); 272 | 273 | switch (prop_id) { 274 | case PROP_CONFIG_FILE: 275 | g_value_set_string (value, config->config_file); 276 | break; 277 | case PROP_PORT: 278 | g_value_set_int (value, config->aur_port); 279 | break; 280 | case PROP_RTSP_PORT: 281 | g_value_set_int (value, config->rtsp_port); 282 | break; 283 | case PROP_DATABASE: 284 | g_value_set_string (value, config->database_location); 285 | break; 286 | case PROP_PLAYLIST: 287 | g_value_set_string (value, config->playlist_location); 288 | break; 289 | default: 290 | G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); 291 | break; 292 | } 293 | } 294 | 295 | AurConfig * 296 | aur_config_new (const gchar *config_file) 297 | { 298 | /* FIXME: return NULL if config file loading failed? */ 299 | return g_object_new (AUR_TYPE_CONFIG, "config-file", config_file, NULL); 300 | } 301 | -------------------------------------------------------------------------------- /src/server/aur-config.h: -------------------------------------------------------------------------------- 1 | /* GStreamer 2 | * Copyright (C) 2012-2014 Jan Schmidt 3 | * 4 | * This library is free software; you can redistribute it and/or 5 | * modify it under the terms of the GNU Library General Public 6 | * License as published by the Free Software Foundation; either 7 | * version 2 of the License, or (at your option) any later version. 8 | * 9 | * This library is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 12 | * Library General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU Library General Public 15 | * License along with this library; if not, write to the 16 | * Free Software Foundation, Inc., 59 Temple Place - Suite 330, 17 | * Boston, MA 02111-1307, USA. 18 | */ 19 | 20 | #ifndef __AUR_CONFIG_H__ 21 | #define __AUR_CONFIG_H__ 22 | 23 | #include 24 | #include 25 | 26 | #include 27 | 28 | G_BEGIN_DECLS 29 | 30 | #define AUR_TYPE_CONFIG (aur_config_get_type ()) 31 | 32 | typedef struct _AurConfigClass AurConfigClass; 33 | 34 | struct _AurConfig 35 | { 36 | GObject parent; 37 | 38 | gchar *config_file; 39 | 40 | int aur_port; 41 | int rtsp_port; 42 | 43 | gchar *database_location; 44 | gchar *playlist_location; 45 | }; 46 | 47 | struct _AurConfigClass 48 | { 49 | GObjectClass parent; 50 | }; 51 | 52 | GType aur_config_get_type(void); 53 | AurConfig *aur_config_new(const char *config_path); 54 | 55 | G_END_DECLS 56 | 57 | #endif 58 | -------------------------------------------------------------------------------- /src/server/aur-http-resource.c: -------------------------------------------------------------------------------- 1 | /* GStreamer 2 | * Copyright (C) 2012-2014 Jan Schmidt 3 | * 4 | * This library is free software; you can redistribute it and/or 5 | * modify it under the terms of the GNU Library General Public 6 | * License as published by the Free Software Foundation; either 7 | * version 2 of the License, or (at your option) any later version. 8 | * 9 | * This library is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 12 | * Library General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU Library General Public 15 | * License along with this library; if not, write to the 16 | * Free Software Foundation, Inc., 59 Temple Place - Suite 330, 17 | * Boston, MA 02111-1307, USA. 18 | */ 19 | 20 | #ifdef HAVE_CONFIG_H 21 | #include "config.h" 22 | #endif 23 | 24 | #include 25 | #include 26 | #include 27 | #include 28 | #include 29 | 30 | #include "aur-resource.h" 31 | #include "aur-http-resource.h" 32 | 33 | #if 0 34 | #define DEBUG_PRINT(...) g_print (__VA_ARGS__); 35 | #else 36 | #define DEBUG_PRINT(...) 37 | #endif 38 | 39 | G_DEFINE_TYPE (AurHttpResource, aur_http_resource, G_TYPE_OBJECT); 40 | 41 | enum 42 | { 43 | PROP_0, 44 | PROP_SOURCE_FILE, 45 | PROP_LAST 46 | }; 47 | 48 | static gint resources_open = 0; 49 | 50 | static void aur_http_resource_set_property (GObject * object, guint prop_id, 51 | const GValue * value, GParamSpec * pspec); 52 | static void aur_http_resource_get_property (GObject * object, guint prop_id, 53 | GValue * value, GParamSpec * pspec); 54 | 55 | typedef struct _AurTransfer 56 | { 57 | AurHttpResource *resource; 58 | } AurTransfer; 59 | 60 | static gboolean 61 | aur_http_resource_open (AurHttpResource * resource) 62 | { 63 | if (resource->data == NULL) { 64 | GError *error = NULL; 65 | 66 | resources_open++; 67 | 68 | if (g_file_is_native (resource->source_file)) { 69 | gchar *local_path; 70 | 71 | local_path = g_file_get_path (resource->source_file); 72 | g_assert (local_path != NULL); 73 | 74 | DEBUG_PRINT ("Opening resource %s. %d now open\n", local_path, 75 | resources_open); 76 | 77 | resource->data = g_mapped_file_new (local_path, FALSE, &error); 78 | 79 | if (resource->data == NULL) { 80 | g_message ("Failed to open resource %s: %s", local_path, 81 | error->message); 82 | g_error_free (error); 83 | g_free (local_path); 84 | resources_open--; 85 | 86 | return FALSE; 87 | } 88 | 89 | g_free (local_path); 90 | } else { 91 | /* Non-native files can't be mmap()ped. */ 92 | resource->data = NULL; 93 | } 94 | } 95 | g_object_ref (resource); 96 | resource->use_count++; 97 | return TRUE; 98 | } 99 | 100 | static void 101 | aur_http_resource_close (AurHttpResource * resource) 102 | { 103 | if (resource->use_count) { 104 | resource->use_count--; 105 | if (resource->use_count == 0) { 106 | resources_open--; 107 | 108 | if (g_file_is_native (resource->source_file)) { 109 | gchar *local_path; 110 | 111 | /* Release the mmap() on the local file. */ 112 | local_path = g_file_get_path (resource->source_file); 113 | DEBUG_PRINT ("Releasing resource %s. %d now open\n", local_path, 114 | resources_open); 115 | #if GLIB_CHECK_VERSION(2,22,0) 116 | g_mapped_file_unref (resource->data); 117 | #else 118 | g_mapped_file_free (resource->data); 119 | #endif 120 | resource->data = NULL; 121 | 122 | DEBUG_PRINT ("closed resource %s (%p) use count now %d\n", 123 | local_path, resource, resource->use_count); 124 | g_free (local_path); 125 | } else { 126 | /* Non-native file. */ 127 | resource->data = NULL; 128 | } 129 | } 130 | } 131 | 132 | g_object_unref (resource); 133 | } 134 | 135 | static AurTransfer * 136 | aur_transfer_new (AurHttpResource *resource) 137 | { 138 | AurTransfer *transfer; 139 | 140 | if (!aur_http_resource_open (resource)) 141 | return NULL; 142 | 143 | transfer = g_new0 (AurTransfer, 1); 144 | transfer->resource = g_object_ref (resource); 145 | 146 | DEBUG_PRINT ("Started transfer with resource %p use count now %d\n", 147 | resource, resource->use_count); 148 | 149 | return transfer; 150 | } 151 | 152 | static void 153 | aur_transfer_free (AurTransfer *transfer) 154 | { 155 | aur_http_resource_close (transfer->resource); 156 | 157 | DEBUG_PRINT ("Completed transfer of %p. Use count now %d\n", 158 | transfer->resource, transfer->resource->use_count); 159 | 160 | g_object_unref (transfer->resource); 161 | g_free (transfer); 162 | } 163 | 164 | void 165 | aur_http_resource_new_transfer (AurHttpResource * resource, SoupMessage * msg) 166 | { 167 | /* Create a new transfer structure, and pass the contents of our 168 | * resource to it */ 169 | AurTransfer *transfer; 170 | SoupBuffer *buffer; 171 | gchar *local_path; 172 | 173 | /* Non-local files are implemented as a HTTP redirect. 174 | * 175 | * FIXME: This isn't an ideal solution, as clients may not be able to access 176 | * exactly the same resources as the server (due to network configuration or 177 | * access restrictions). It would be better to proxy the file through the 178 | * daemon, but that's an order of magnitude more complex than a simple HTTP 179 | * redirect. */ 180 | if (!g_file_is_native (resource->source_file)) { 181 | gchar *resource_uri = g_file_get_uri (resource->source_file); 182 | soup_message_set_redirect (msg, SOUP_STATUS_TEMPORARY_REDIRECT, resource_uri); 183 | g_free (resource_uri); 184 | 185 | return; 186 | } 187 | 188 | transfer = aur_transfer_new (resource); 189 | 190 | if (!transfer) { 191 | soup_message_set_status (msg, SOUP_STATUS_INTERNAL_SERVER_ERROR); 192 | return; 193 | } 194 | 195 | local_path = g_file_get_path (resource->source_file); 196 | g_assert (local_path != NULL); 197 | 198 | buffer = soup_buffer_new_with_owner ( 199 | g_mapped_file_get_contents (transfer->resource->data), 200 | g_mapped_file_get_length (transfer->resource->data), 201 | transfer, (GDestroyNotify) aur_transfer_free); 202 | 203 | soup_message_set_status (msg, SOUP_STATUS_OK); 204 | soup_message_headers_replace (msg->response_headers, 205 | "Content-Type", aur_resource_get_mime_type (local_path)); 206 | soup_message_body_append_buffer (msg->response_body, buffer); 207 | soup_buffer_free (buffer); 208 | 209 | g_free (local_path); 210 | } 211 | 212 | static void 213 | aur_http_resource_init (G_GNUC_UNUSED AurHttpResource * resource) 214 | { 215 | } 216 | 217 | static void 218 | aur_http_resource_class_init (AurHttpResourceClass * resource_class) 219 | { 220 | GObjectClass *gobject_class = (GObjectClass *) (resource_class); 221 | 222 | gobject_class->set_property = aur_http_resource_set_property; 223 | gobject_class->get_property = aur_http_resource_get_property; 224 | 225 | g_object_class_install_property (gobject_class, PROP_SOURCE_FILE, 226 | g_param_spec_object ("source-file", "Source File", 227 | "Source file resource", G_TYPE_FILE, G_PARAM_READWRITE)); 228 | } 229 | 230 | static void 231 | aur_http_resource_set_property (GObject * object, guint prop_id, 232 | const GValue * value, GParamSpec * pspec) 233 | { 234 | AurHttpResource *resource = (AurHttpResource *) (object); 235 | 236 | switch (prop_id) { 237 | case PROP_SOURCE_FILE: 238 | g_clear_object (&resource->source_file); 239 | resource->source_file = g_value_dup_object (value); 240 | break; 241 | default: 242 | G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); 243 | break; 244 | } 245 | } 246 | 247 | static void 248 | aur_http_resource_get_property (GObject * object, guint prop_id, 249 | GValue * value, GParamSpec * pspec) 250 | { 251 | AurHttpResource *resource = (AurHttpResource *) (object); 252 | 253 | switch (prop_id) { 254 | case PROP_SOURCE_FILE: 255 | g_value_set_object (value, resource->source_file); 256 | break; 257 | default: 258 | G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); 259 | break; 260 | } 261 | } 262 | -------------------------------------------------------------------------------- /src/server/aur-http-resource.h: -------------------------------------------------------------------------------- 1 | /* GStreamer 2 | * Copyright (C) 2012-2014 Jan Schmidt 3 | * 4 | * This library is free software; you can redistribute it and/or 5 | * modify it under the terms of the GNU Library General Public 6 | * License as published by the Free Software Foundation; either 7 | * version 2 of the License, or (at your option) any later version. 8 | * 9 | * This library is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 12 | * Library General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU Library General Public 15 | * License along with this library; if not, write to the 16 | * Free Software Foundation, Inc., 59 Temple Place - Suite 330, 17 | * Boston, MA 02111-1307, USA. 18 | */ 19 | 20 | #ifndef __AUR_HTTP_RESOURCE_H__ 21 | #define __AUR_HTTP_RESOURCE_H__ 22 | 23 | #include 24 | #include 25 | #include 26 | 27 | #include 28 | 29 | G_BEGIN_DECLS 30 | 31 | #define AUR_TYPE_HTTP_RESOURCE (aur_http_resource_get_type ()) 32 | 33 | typedef struct _AurHttpResourceClass AurHttpResourceClass; 34 | 35 | struct _AurHttpResource 36 | { 37 | GObject parent; 38 | 39 | GFile *source_file; 40 | guint use_count; 41 | GMappedFile *data; 42 | }; 43 | 44 | struct _AurHttpResourceClass 45 | { 46 | GObjectClass parent; 47 | }; 48 | 49 | GType aur_http_resource_get_type(void); 50 | 51 | void aur_http_resource_new_transfer (AurHttpResource *resource, SoupMessage *msg); 52 | 53 | G_END_DECLS 54 | #endif 55 | -------------------------------------------------------------------------------- /src/server/aur-manager.h: -------------------------------------------------------------------------------- 1 | /* GStreamer 2 | * Copyright (C) 2012-2014 Jan Schmidt 3 | * 4 | * This library is free software; you can redistribute it and/or 5 | * modify it under the terms of the GNU Library General Public 6 | * License as published by the Free Software Foundation; either 7 | * version 2 of the License, or (at your option) any later version. 8 | * 9 | * This library is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 12 | * Library General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU Library General Public 15 | * License along with this library; if not, write to the 16 | * Free Software Foundation, Inc., 59 Temple Place - Suite 330, 17 | * Boston, MA 02111-1307, USA. 18 | */ 19 | #ifndef __AUR_MANAGER_H__ 20 | #define __AUR_MANAGER_H__ 21 | 22 | #include 23 | #include 24 | #include 25 | #include 26 | 27 | #ifdef HAVE_GST_RTSP 28 | #include 29 | #endif 30 | 31 | #include 32 | #include "aur-avahi.h" 33 | 34 | G_BEGIN_DECLS 35 | 36 | #define AUR_TYPE_MANAGER (aur_manager_get_type ()) 37 | 38 | typedef struct _AurManagerClass AurManagerClass; 39 | 40 | struct _AurManager 41 | { 42 | GObject parent; 43 | 44 | AurServer *server; 45 | GstNetTimeProvider *net_clock; 46 | #ifdef HAVE_GST_RTSP 47 | GstRTSPServer *rtsp; 48 | #endif 49 | int rtsp_port; 50 | 51 | AurAvahi *avahi; 52 | 53 | AurConfig *config; 54 | AurMediaDB *media_db; 55 | 56 | GPtrArray *playlist; 57 | gboolean paused; 58 | guint current_resource; 59 | GFile *custom_file; 60 | gchar *language; 61 | 62 | guint next_player_id; 63 | GList *player_info; 64 | 65 | GList *ctrl_clients; 66 | 67 | GstClockTime base_time; 68 | GstClockTime position; 69 | 70 | gdouble current_volume; 71 | 72 | guint ping_timeout; 73 | }; 74 | 75 | struct _AurManagerClass 76 | { 77 | GObjectClass parent; 78 | }; 79 | 80 | GType aur_manager_get_type(void); 81 | AurManager *aur_manager_new(const char *config_file); 82 | 83 | G_END_DECLS 84 | #endif 85 | -------------------------------------------------------------------------------- /src/server/aur-media-db.c: -------------------------------------------------------------------------------- 1 | /* GStreamer 2 | * Copyright (C) 2012-2014 Jan Schmidt 3 | * 4 | * This library is free software; you can redistribute it and/or 5 | * modify it under the terms of the GNU Library General Public 6 | * License as published by the Free Software Foundation; either 7 | * version 2 of the License, or (at your option) any later version. 8 | * 9 | * This library is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 12 | * Library General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU Library General Public 15 | * License along with this library; if not, write to the 16 | * Free Software Foundation, Inc., 59 Temple Place - Suite 330, 17 | * Boston, MA 02111-1307, USA. 18 | */ 19 | 20 | /* 21 | * Media database interface. 22 | * 23 | * The database has three tables: files, paths and songs. The songs table isn't 24 | * used at all. The files and paths tables store paths and basenames for media 25 | * files, split up to reduce path duplication. However, URIs may also be stored 26 | * in the database, in a slightly different format. In the case of storing 27 | * normal paths or local URIs, the path and basename are calculated and split 28 | * between the files and paths tables. The files.base_path_id is positive. In 29 | * the case of storing non-local URIs, the entire URI is stored in 30 | * files.filename and files.base_path_id is 0. 31 | */ 32 | 33 | #ifdef HAVE_CONFIG_H 34 | #include "config.h" 35 | #endif 36 | 37 | #include "aur-media-db.h" 38 | 39 | #define AUR_TYPE_MEDIA_DB (aur_media_db_get_type ()) 40 | 41 | typedef struct _AurMediaDBClass AurMediaDBClass; 42 | 43 | enum 44 | { 45 | PROP_0 = 0, 46 | PROP_DB_FILE, 47 | PROP_LAST 48 | }; 49 | 50 | struct _AurMediaDBPriv 51 | { 52 | GObject parent; 53 | 54 | sqlite3 *handle; 55 | gboolean errored; 56 | gchar *db_file; 57 | }; 58 | 59 | struct _AurMediaDBClass 60 | { 61 | GObjectClass parent; 62 | }; 63 | 64 | static GType aur_media_db_get_type (void); 65 | static void aur_media_db_finalize (GObject * object); 66 | static gboolean media_db_create_tables (AurMediaDB * media_db); 67 | static void aur_media_db_set_property (GObject * object, guint prop_id, 68 | const GValue * value, GParamSpec * pspec); 69 | static void aur_media_db_get_property (GObject * object, guint prop_id, 70 | GValue * value, GParamSpec * pspec); 71 | 72 | G_DEFINE_TYPE (AurMediaDB, aur_media_db, G_TYPE_OBJECT); 73 | 74 | static void 75 | aur_media_db_init (AurMediaDB * media_db) 76 | { 77 | media_db->priv = G_TYPE_INSTANCE_GET_PRIVATE (media_db, 78 | AUR_TYPE_MEDIA_DB, AurMediaDBPriv); 79 | } 80 | 81 | static void 82 | aur_media_db_constructed (G_GNUC_UNUSED GObject * object) 83 | { 84 | AurMediaDB *media_db = (AurMediaDB *) (object); 85 | sqlite3 *handle = NULL; 86 | gchar *dir; 87 | 88 | if (G_OBJECT_CLASS (aur_media_db_parent_class)->constructed != NULL) 89 | G_OBJECT_CLASS (aur_media_db_parent_class)->constructed (object); 90 | 91 | dir = g_path_get_dirname (media_db->priv->db_file); 92 | g_mkdir_with_parents (dir, 0755); 93 | g_free (dir); 94 | 95 | if (sqlite3_open (media_db->priv->db_file, &handle) != SQLITE_OK) { 96 | g_warning ("Could not open media DB %s\n", media_db->priv->db_file); 97 | media_db->priv->errored = TRUE; 98 | } 99 | media_db->priv->handle = handle; 100 | 101 | if (!media_db_create_tables (media_db)) 102 | media_db->priv->errored = TRUE; 103 | g_print ("media DB ready at %s with %d entries\n", 104 | media_db->priv->db_file, 105 | aur_media_db_get_file_count (media_db)); 106 | } 107 | 108 | static void 109 | aur_media_db_class_init (AurMediaDBClass * media_db_class) 110 | { 111 | GObjectClass *object_class = (GObjectClass *) (media_db_class); 112 | 113 | object_class->constructed = aur_media_db_constructed; 114 | object_class->set_property = aur_media_db_set_property; 115 | object_class->get_property = aur_media_db_get_property; 116 | 117 | object_class->finalize = aur_media_db_finalize; 118 | 119 | g_object_class_install_property (object_class, PROP_DB_FILE, 120 | g_param_spec_string ("db-file", "Database file", 121 | "Location for media DB file", NULL, 122 | G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY)); 123 | g_type_class_add_private (object_class, sizeof (AurMediaDBPriv)); 124 | } 125 | 126 | static void 127 | aur_media_db_finalize (GObject * object) 128 | { 129 | AurMediaDB *media_db = (AurMediaDB *) (object); 130 | 131 | if (media_db->priv->handle) 132 | sqlite3_close (media_db->priv->handle); 133 | } 134 | 135 | static void 136 | aur_media_db_set_property (GObject * object, guint prop_id, 137 | const GValue * value, GParamSpec * pspec) 138 | { 139 | AurMediaDB *media_db = (AurMediaDB *) (object); 140 | 141 | switch (prop_id) { 142 | case PROP_DB_FILE: 143 | g_free (media_db->priv->db_file); 144 | media_db->priv->db_file = g_value_dup_string (value); 145 | break; 146 | default: 147 | G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); 148 | break; 149 | } 150 | } 151 | 152 | static void 153 | aur_media_db_get_property (GObject * object, guint prop_id, 154 | GValue * value, GParamSpec * pspec) 155 | { 156 | AurMediaDB *media_db = (AurMediaDB *) (object); 157 | 158 | switch (prop_id) { 159 | case PROP_DB_FILE: 160 | g_value_set_string (value, media_db->priv->db_file); 161 | break; 162 | default: 163 | G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); 164 | break; 165 | } 166 | } 167 | 168 | static gboolean 169 | media_db_create_tables (AurMediaDB * media_db) 170 | { 171 | sqlite3 *handle = media_db->priv->handle; 172 | if (sqlite3_exec (handle, 173 | "Create table if not exists paths" 174 | "(id INTEGER PRIMARY KEY, base_path TEXT)", 175 | NULL, NULL, NULL) != SQLITE_OK) 176 | return FALSE; 177 | if (sqlite3_exec (handle, 178 | "Create table if not exists files" 179 | "(id INTEGER PRIMARY KEY, base_path_id INTEGER, " 180 | "filename TEXT, timestamp TEXT, " 181 | "duration INTEGER, is_video INTEGER)", NULL, NULL, NULL) != SQLITE_OK) 182 | return FALSE; 183 | if (sqlite3_exec (handle, 184 | "Create table if not exists songs" 185 | "(id INTEGER PRIMARY KEY, media_id INTEGER, " 186 | "timestamp TEXT, duration INTEGER, " 187 | "is_video INTEGER)", NULL, NULL, NULL) != SQLITE_OK) 188 | return FALSE; 189 | 190 | return TRUE; 191 | } 192 | 193 | AurMediaDB * 194 | aur_media_db_new (const char *db_path) 195 | { 196 | AurMediaDB *media_db = NULL; 197 | 198 | media_db = g_object_new (AUR_TYPE_MEDIA_DB, "db-file", db_path, NULL); 199 | if (media_db == NULL) 200 | return NULL; 201 | 202 | if (media_db->priv->errored) { 203 | g_object_unref (media_db); 204 | media_db = NULL; 205 | } 206 | 207 | return media_db; 208 | } 209 | 210 | static guint64 211 | aur_media_path_to_id (AurMediaDB * media_db, const gchar * path) 212 | { 213 | sqlite3_stmt *stmt = NULL; 214 | sqlite3_stmt *insert_stmt = NULL; 215 | guint64 path_id = (guint64) (-1); 216 | sqlite3 *handle = media_db->priv->handle; 217 | 218 | if (sqlite3_prepare (handle, 219 | "select id from paths where base_path=?", -1, &stmt, 220 | NULL) != SQLITE_OK) 221 | goto done; 222 | 223 | if (sqlite3_bind_text (stmt, 1, g_strdup (path), -1, g_free) != SQLITE_OK) 224 | goto done; 225 | 226 | if (sqlite3_step (stmt) == SQLITE_ROW) { 227 | path_id = sqlite3_column_int64 (stmt, 0); 228 | goto done; 229 | } 230 | 231 | /* Row not found, insert it */ 232 | if (sqlite3_prepare (handle, 233 | "insert into paths (base_path) VALUES (?)", 234 | -1, &insert_stmt, NULL) != SQLITE_OK) 235 | goto done; 236 | 237 | if (sqlite3_bind_text (insert_stmt, 1, 238 | g_strdup (path), -1, g_free) != SQLITE_OK) 239 | goto done; 240 | 241 | if (sqlite3_step (insert_stmt) == SQLITE_DONE) { 242 | path_id = sqlite3_last_insert_rowid (handle); 243 | goto done; 244 | } 245 | 246 | done: 247 | if (insert_stmt) 248 | sqlite3_finalize (insert_stmt); 249 | if (stmt) 250 | sqlite3_finalize (stmt); 251 | return path_id; 252 | } 253 | 254 | static guint64 255 | aur_media_file_to_id (AurMediaDB * media_db, guint64 path_id, 256 | const gchar * file) 257 | { 258 | sqlite3_stmt *stmt = NULL; 259 | sqlite3_stmt *insert_stmt = NULL; 260 | guint64 file_id = (guint64) (-1); 261 | sqlite3 *handle = media_db->priv->handle; 262 | 263 | if (sqlite3_prepare (handle, 264 | "select id from files where base_path_id=? and filename=?", 265 | -1, &stmt, NULL) != SQLITE_OK) 266 | goto done; 267 | 268 | if (sqlite3_bind_int64 (stmt, 1, path_id) != SQLITE_OK) 269 | goto done; 270 | if (sqlite3_bind_text (stmt, 2, g_strdup (file), -1, g_free) != SQLITE_OK) 271 | goto done; 272 | 273 | if (sqlite3_step (stmt) == SQLITE_ROW) { 274 | file_id = sqlite3_column_int64 (stmt, 0); 275 | goto done; 276 | } 277 | 278 | /* Row not found, insert it */ 279 | if (sqlite3_prepare (handle, 280 | "insert into files (base_path_id, filename) VALUES (?,?)", 281 | -1, &insert_stmt, NULL) != SQLITE_OK) 282 | goto done; 283 | 284 | if (sqlite3_bind_int64 (insert_stmt, 1, path_id) != SQLITE_OK) 285 | goto done; 286 | if (sqlite3_bind_text (insert_stmt, 2, 287 | g_strdup (file), -1, g_free) != SQLITE_OK) 288 | goto done; 289 | 290 | if (sqlite3_step (insert_stmt) == SQLITE_DONE) { 291 | file_id = sqlite3_last_insert_rowid (handle); 292 | goto done; 293 | } 294 | 295 | done: 296 | if (insert_stmt) 297 | sqlite3_finalize (insert_stmt); 298 | if (stmt) 299 | sqlite3_finalize (stmt); 300 | return file_id; 301 | } 302 | 303 | void 304 | aur_media_db_add_file (AurMediaDB * media_db, GFile *file) 305 | { 306 | gchar *basename; 307 | guint64 path_id; 308 | 309 | if (g_file_is_native (file)) { 310 | gchar *filename, *dirname; 311 | 312 | /* Old-style local file. Split it into its basename and filename and 313 | * insert them into different tables. */ 314 | filename = g_file_get_path (file); 315 | dirname = g_path_get_dirname (filename); 316 | 317 | path_id = aur_media_path_to_id (media_db, dirname); 318 | basename = g_path_get_basename (filename); 319 | 320 | g_free (dirname); 321 | g_free (filename); 322 | } else { 323 | /* New-style URI. Insert the entire URI into the files table with a path ID 324 | * of 0 to indicate it has no associated entry in the paths table. */ 325 | basename = g_file_get_uri (file); 326 | path_id = 0; 327 | } 328 | 329 | aur_media_file_to_id (media_db, path_id, basename); 330 | 331 | g_free (basename); 332 | } 333 | 334 | guint 335 | aur_media_db_get_file_count (AurMediaDB * media_db) 336 | { 337 | sqlite3_stmt *stmt = NULL; 338 | gint count = 0; 339 | sqlite3 *handle = media_db->priv->handle; 340 | 341 | if (sqlite3_prepare (handle, 342 | "select count(*) from files", -1, &stmt, NULL) != SQLITE_OK) 343 | goto done; 344 | 345 | if (sqlite3_step (stmt) == SQLITE_ROW) { 346 | count = sqlite3_column_int (stmt, 0); 347 | goto done; 348 | } 349 | 350 | done: 351 | return count; 352 | } 353 | 354 | GFile * 355 | aur_media_db_get_file_by_id (AurMediaDB * media_db, guint id) 356 | { 357 | sqlite3_stmt *stmt = NULL; 358 | sqlite3 *handle = media_db->priv->handle; 359 | gchar *ret_path = NULL; 360 | 361 | if (id < 1) 362 | goto done; 363 | 364 | if (sqlite3_prepare (handle, 365 | "select base_path, filename, files.base_path_id from paths, files where " 366 | "(paths.id = files.base_path_id or files.base_path_id = 0) and files.id = ?" 367 | "limit 1", -1, &stmt, NULL) != SQLITE_OK) 368 | goto done; 369 | 370 | if (sqlite3_bind_int64 (stmt, 1, id - 1) != SQLITE_OK) 371 | goto done; 372 | 373 | if (sqlite3_step (stmt) == SQLITE_ROW) { 374 | const gchar *base_path, *filename; 375 | gint base_path_id; 376 | 377 | base_path_id = sqlite3_column_int (stmt, 2); 378 | 379 | if (base_path_id > 0) { 380 | /* Old-style local file. */ 381 | base_path = (gchar *) sqlite3_column_text (stmt, 0); 382 | filename = (gchar *) sqlite3_column_text (stmt, 1); 383 | ret_path = g_build_filename (base_path, filename, NULL); 384 | } else { 385 | /* New-style URI. The base_path is empty. */ 386 | ret_path = g_strdup ((char *) sqlite3_column_text (stmt, 1)); 387 | } 388 | 389 | goto done; 390 | } 391 | 392 | done: 393 | if (ret_path == NULL) 394 | return NULL; 395 | 396 | return g_file_new_for_path (ret_path); 397 | } 398 | 399 | void 400 | aur_media_db_begin_transaction (AurMediaDB *media_db) 401 | { 402 | sqlite3_stmt *stmt = NULL; 403 | sqlite3 *handle = media_db->priv->handle; 404 | 405 | if (sqlite3_prepare (handle, 406 | "begin transaction", -1, &stmt, NULL) != SQLITE_OK) 407 | return; 408 | sqlite3_step (stmt); 409 | } 410 | 411 | void 412 | aur_media_db_commit_transaction (AurMediaDB *media_db) 413 | { 414 | sqlite3_stmt *stmt = NULL; 415 | sqlite3 *handle = media_db->priv->handle; 416 | 417 | if (sqlite3_prepare (handle, 418 | "commit transaction", -1, &stmt, NULL) != SQLITE_OK) 419 | return; 420 | sqlite3_step (stmt); 421 | 422 | } 423 | 424 | -------------------------------------------------------------------------------- /src/server/aur-media-db.h: -------------------------------------------------------------------------------- 1 | /* GStreamer 2 | * Copyright (C) 2012-2014 Jan Schmidt 3 | * 4 | * This library is free software; you can redistribute it and/or 5 | * modify it under the terms of the GNU Library General Public 6 | * License as published by the Free Software Foundation; either 7 | * version 2 of the License, or (at your option) any later version. 8 | * 9 | * This library is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 12 | * Library General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU Library General Public 15 | * License along with this library; if not, write to the 16 | * Free Software Foundation, Inc., 59 Temple Place - Suite 330, 17 | * Boston, MA 02111-1307, USA. 18 | */ 19 | 20 | #ifndef __AUR_MEDIA_DB_H__ 21 | #define __AUR_MEDIA_DB_H__ 22 | 23 | #include 24 | #include 25 | #include 26 | #include 27 | 28 | #include 29 | 30 | G_BEGIN_DECLS 31 | 32 | typedef struct _AurMediaDBPriv AurMediaDBPriv; 33 | 34 | struct _AurMediaDB 35 | { 36 | GObject parent; 37 | AurMediaDBPriv *priv; 38 | }; 39 | 40 | AurMediaDB *aur_media_db_new(const char *db_path); 41 | void aur_media_db_add_file (AurMediaDB *media_db, GFile *file); 42 | guint aur_media_db_get_file_count (AurMediaDB *media_db); 43 | GFile *aur_media_db_get_file_by_id (AurMediaDB *media_db, guint id); 44 | void aur_media_db_begin_transaction (AurMediaDB *media_db); 45 | void aur_media_db_commit_transaction (AurMediaDB *media_db); 46 | 47 | G_END_DECLS 48 | 49 | #endif 50 | -------------------------------------------------------------------------------- /src/server/aur-resource.c: -------------------------------------------------------------------------------- 1 | /* GStreamer 2 | * Copyright (C) 2012-2014 Jan Schmidt 3 | * 4 | * This library is free software; you can redistribute it and/or 5 | * modify it under the terms of the GNU Library General Public 6 | * License as published by the Free Software Foundation; either 7 | * version 2 of the License, or (at your option) any later version. 8 | * 9 | * This library is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 12 | * Library General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU Library General Public 15 | * License along with this library; if not, write to the 16 | * Free Software Foundation, Inc., 59 Temple Place - Suite 330, 17 | * Boston, MA 02111-1307, USA. 18 | */ 19 | 20 | #ifdef HAVE_CONFIG_H 21 | #include "config.h" 22 | #endif 23 | 24 | #include "aur-resource.h" 25 | 26 | const gchar * 27 | aur_resource_get_mime_type (const gchar *filename) 28 | { 29 | const gchar *extension; 30 | 31 | extension = g_strrstr (filename, "."); 32 | if (extension) { 33 | if (g_str_equal (extension, ".html")) 34 | return "text/html"; 35 | if (g_str_equal (extension, ".png")) 36 | return "image/png"; 37 | if (g_str_equal (extension, ".css")) 38 | return "text/css"; 39 | if (g_str_equal (extension, ".jpg")) 40 | return "image/jpeg"; 41 | if (g_str_equal (extension, ".js")) 42 | return "text/javascript"; 43 | } 44 | 45 | return "text/plain"; 46 | } 47 | -------------------------------------------------------------------------------- /src/server/aur-resource.h: -------------------------------------------------------------------------------- 1 | /* GStreamer 2 | * Copyright (C) 2012-2014 Jan Schmidt 3 | * 4 | * This library is free software; you can redistribute it and/or 5 | * modify it under the terms of the GNU Library General Public 6 | * License as published by the Free Software Foundation; either 7 | * version 2 of the License, or (at your option) any later version. 8 | * 9 | * This library is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 12 | * Library General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU Library General Public 15 | * License along with this library; if not, write to the 16 | * Free Software Foundation, Inc., 59 Temple Place - Suite 330, 17 | * Boston, MA 02111-1307, USA. 18 | */ 19 | 20 | #ifndef __AUR_RESOURCE_H__ 21 | #define __AUR_RESOURCE_H__ 22 | 23 | #include 24 | 25 | const gchar * 26 | aur_resource_get_mime_type (const gchar *filename); 27 | 28 | #endif 29 | -------------------------------------------------------------------------------- /src/server/aur-server-client.h: -------------------------------------------------------------------------------- 1 | /* GStreamer 2 | * Copyright (C) 2012-2014 Jan Schmidt 3 | * 4 | * This library is free software; you can redistribute it and/or 5 | * modify it under the terms of the GNU Library General Public 6 | * License as published by the Free Software Foundation; either 7 | * version 2 of the License, or (at your option) any later version. 8 | * 9 | * This library is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 12 | * Library General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU Library General Public 15 | * License along with this library; if not, write to the 16 | * Free Software Foundation, Inc., 59 Temple Place - Suite 330, 17 | * Boston, MA 02111-1307, USA. 18 | */ 19 | 20 | #ifndef __AUR_SERVER_CLIENT_H__ 21 | #define __AUR_SERVER_CLIENT_H__ 22 | 23 | #include 24 | #include 25 | #include 26 | 27 | #include 28 | #include 29 | 30 | G_BEGIN_DECLS 31 | 32 | #define AUR_TYPE_SERVER_CLIENT (aur_server_client_get_type ()) 33 | 34 | typedef struct _AurServerClientClass AurServerClientClass; 35 | typedef enum _AurServerClientType AurServerClientType; 36 | 37 | enum _AurServerClientType { 38 | AUR_SERVER_CLIENT_CHUNKED, 39 | AUR_SERVER_CLIENT_WEBSOCKET, 40 | AUR_SERVER_CLIENT_SINGLE 41 | }; 42 | 43 | struct _AurServerClient 44 | { 45 | AurWebSocketParser parent; 46 | 47 | AurServerClientType type; 48 | gboolean fired_conn_lost; 49 | gboolean need_body_complete; 50 | 51 | guint conn_id; 52 | SoupMessage *event_pipe; 53 | SoupServer *soup; 54 | 55 | /* For talking to websocket clients */ 56 | gint websocket_protocol; 57 | 58 | GSocket *socket; 59 | gchar *host; 60 | 61 | GIOChannel *io; 62 | guint io_watch; 63 | 64 | gchar *out_buf; 65 | gsize out_bufsize; 66 | 67 | GList *pending_msgs; 68 | 69 | gulong net_event_sig; 70 | gulong disco_sig; 71 | gulong wrote_info_sig; 72 | }; 73 | 74 | struct _AurServerClientClass 75 | { 76 | AurWebSocketParserClass parent; 77 | }; 78 | 79 | GType aur_server_client_get_type(void); 80 | 81 | AurServerClient *aur_server_client_new (SoupServer *soup, 82 | SoupMessage *msg, SoupClientContext *context); 83 | AurServerClient *aur_server_client_new_single (SoupServer * soup, 84 | SoupMessage * msg, SoupClientContext * context); 85 | 86 | void aur_server_client_send_message (AurServerClient *client, 87 | gchar *body, gsize len); 88 | 89 | const gchar *aur_server_client_get_host (AurServerClient *client); 90 | 91 | G_END_DECLS 92 | 93 | #endif 94 | -------------------------------------------------------------------------------- /src/server/aur-server.c: -------------------------------------------------------------------------------- 1 | /* GStreamer 2 | * Copyright (C) 2012-2014 Jan Schmidt 3 | * 4 | * This library is free software; you can redistribute it and/or 5 | * modify it under the terms of the GNU Library General Public 6 | * License as published by the Free Software Foundation; either 7 | * version 2 of the License, or (at your option) any later version. 8 | * 9 | * This library is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 12 | * Library General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU Library General Public 15 | * License along with this library; if not, write to the 16 | * Free Software Foundation, Inc., 59 Temple Place - Suite 330, 17 | * Boston, MA 02111-1307, USA. 18 | */ 19 | 20 | #ifdef HAVE_CONFIG_H 21 | #include "config.h" 22 | #endif 23 | 24 | #include 25 | #include 26 | #include 27 | #include 28 | #include 29 | #include 30 | 31 | #include "aur-config.h" 32 | #include "aur-server.h" 33 | #include "aur-server-client.h" 34 | #include "aur-resource.h" 35 | #include "aur-http-resource.h" 36 | 37 | G_DEFINE_TYPE (AurServer, aur_server, G_TYPE_OBJECT); 38 | 39 | enum 40 | { 41 | PROP_0, 42 | PROP_CONFIG, 43 | PROP_CLOCK, 44 | PROP_LAST 45 | }; 46 | 47 | static void aur_server_set_property (GObject * object, guint prop_id, 48 | const GValue * value, GParamSpec * pspec); 49 | static void aur_server_get_property (GObject * object, guint prop_id, 50 | GValue * value, GParamSpec * pspec); 51 | 52 | static void aur_soup_message_set_redirect (SoupMessage * msg, 53 | guint status_code, const char *redirect_uri); 54 | static void aur_server_finalize (GObject * object); 55 | static void aur_server_dispose (GObject * object); 56 | 57 | static void 58 | aur_soup_message_set_redirect (SoupMessage * msg, guint status_code, 59 | const char *redirect_uri) 60 | { 61 | SoupURI *location; 62 | char *location_str; 63 | 64 | location = soup_uri_new_with_base (soup_message_get_uri (msg), redirect_uri); 65 | g_return_if_fail (location != NULL); 66 | 67 | soup_message_set_status (msg, status_code); 68 | location_str = soup_uri_to_string (location, FALSE); 69 | soup_message_headers_replace (msg->response_headers, "Location", 70 | location_str); 71 | g_free (location_str); 72 | soup_uri_free (location); 73 | } 74 | 75 | static void 76 | server_fallback_cb (G_GNUC_UNUSED SoupServer * soup, SoupMessage * msg, 77 | G_GNUC_UNUSED const char *path, G_GNUC_UNUSED GHashTable * query, 78 | G_GNUC_UNUSED SoupClientContext * client, G_GNUC_UNUSED AurServer * server) 79 | { 80 | if (g_str_equal (path, "/")) { 81 | aur_soup_message_set_redirect (msg, SOUP_STATUS_MOVED_PERMANENTLY, "/ui/"); 82 | } else { 83 | soup_message_set_status (msg, SOUP_STATUS_NOT_FOUND); 84 | } 85 | } 86 | 87 | static AurHttpResource * 88 | aur_server_get_resource (AurServer * server, guint resource_id) 89 | { 90 | AurHttpResource *resource = NULL; 91 | 92 | resource = 93 | g_hash_table_lookup (server->resources, GINT_TO_POINTER (resource_id)); 94 | if (resource == NULL) { 95 | resource = 96 | server->get_resource (server, resource_id, 97 | server->get_resource_userdata); 98 | if (resource_id != G_MAXUINT) 99 | g_hash_table_insert (server->resources, GINT_TO_POINTER (resource_id), 100 | resource); 101 | } 102 | return resource; 103 | } 104 | 105 | static void 106 | server_resource_cb (G_GNUC_UNUSED SoupServer * soup, SoupMessage * msg, 107 | const char *path, G_GNUC_UNUSED GHashTable * query, 108 | G_GNUC_UNUSED SoupClientContext * client, AurServer * server) 109 | { 110 | guint resource_id = 0; 111 | AurHttpResource *resource; 112 | 113 | if (!sscanf (path, "/resource/%u", &resource_id)) 114 | goto error; 115 | 116 | resource = aur_server_get_resource (server, resource_id); 117 | if (resource == NULL) 118 | goto error; 119 | 120 | g_print ("Hit on resource %u\n", resource_id); 121 | aur_http_resource_new_transfer (resource, msg); 122 | 123 | return; 124 | error: 125 | soup_message_set_status (msg, SOUP_STATUS_NOT_FOUND); 126 | } 127 | 128 | static gchar * 129 | get_data_filename (const gchar *basename) 130 | { 131 | const gchar * const * dirs; 132 | gchar *filepath = NULL; 133 | gint i; 134 | 135 | dirs = g_get_system_data_dirs (); 136 | 137 | for (i = 0; dirs[i] != NULL; i++) { 138 | GFile *file; 139 | 140 | filepath = g_build_filename (dirs[i], "aurena", "htdocs", basename, NULL); 141 | g_print ("looking for %s\n", filepath); 142 | file = g_file_new_for_path (filepath); 143 | if (g_file_query_exists (file, NULL)) { 144 | g_object_unref (file); 145 | break; 146 | } 147 | 148 | g_object_unref (file); 149 | g_free (filepath); 150 | filepath = NULL; 151 | } 152 | 153 | if (!filepath) { 154 | GFile *file; 155 | 156 | filepath = g_build_filename (g_get_current_dir (), "data", "htdocs", basename, NULL); 157 | g_print ("looking for %s\n", filepath); 158 | file = g_file_new_for_path (filepath); 159 | if (!g_file_query_exists (file, NULL)) { 160 | g_free (filepath); 161 | filepath = NULL; 162 | } 163 | g_object_unref (file); 164 | } 165 | 166 | return filepath; 167 | } 168 | 169 | static void 170 | server_ui_cb (G_GNUC_UNUSED SoupServer * soup, SoupMessage * msg, 171 | const char *path, G_GNUC_UNUSED GHashTable * query, 172 | G_GNUC_UNUSED SoupClientContext * client, G_GNUC_UNUSED AurServer * server) 173 | { 174 | const gchar *file_path; 175 | gchar *filename = NULL; 176 | gchar *contents; 177 | gsize size; 178 | const gchar *mime_type; 179 | 180 | if (!g_str_has_prefix (path, "/ui")) 181 | goto fail; 182 | 183 | file_path = path + 3; 184 | 185 | if (strstr (file_path, "/..")) 186 | goto fail; 187 | 188 | if (g_str_equal (file_path, "")) { 189 | aur_soup_message_set_redirect (msg, SOUP_STATUS_MOVED_PERMANENTLY, "/ui/"); 190 | return; 191 | } 192 | 193 | if (g_str_equal (file_path, "/")) 194 | file_path = "/index.html"; 195 | 196 | filename = get_data_filename (file_path); 197 | 198 | if (!g_file_get_contents (filename, &contents, &size, NULL)) 199 | goto fail; 200 | 201 | mime_type = aur_resource_get_mime_type (filename); 202 | g_print ("Returning %s - %s\n", mime_type, filename); 203 | 204 | soup_message_set_response (msg, mime_type, SOUP_MEMORY_TAKE, contents, size); 205 | soup_message_set_status (msg, SOUP_STATUS_OK); 206 | 207 | g_free (filename); 208 | return; 209 | 210 | fail: 211 | g_free (filename); 212 | soup_message_set_status (msg, SOUP_STATUS_NOT_FOUND); 213 | } 214 | 215 | static void 216 | aur_server_init (AurServer * server) 217 | { 218 | server->resources = 219 | g_hash_table_new_full (g_direct_hash, 220 | g_direct_equal, NULL, g_object_unref); 221 | } 222 | 223 | static void 224 | aur_server_constructed (GObject * object) 225 | { 226 | AurServer *server = (AurServer *) (object); 227 | //SoupSocket *socket; 228 | gint port; 229 | 230 | if (G_OBJECT_CLASS (aur_server_parent_class)->constructed != NULL) 231 | G_OBJECT_CLASS (aur_server_parent_class)->constructed (object); 232 | 233 | g_object_get (server->config, "aur-port", &port, NULL); 234 | 235 | server->soup = soup_server_new (NULL, NULL); 236 | 237 | soup_server_add_handler (server->soup, "/", 238 | (SoupServerCallback) server_fallback_cb, 239 | g_object_ref (server), g_object_unref); 240 | soup_server_add_handler (server->soup, "/ui", 241 | (SoupServerCallback) server_ui_cb, g_object_ref (server), g_object_unref); 242 | 243 | soup_server_add_handler (server->soup, "/resource", 244 | (SoupServerCallback) server_resource_cb, 245 | g_object_ref (server), g_object_unref); 246 | 247 | #if 0 248 | socket = soup_server_get_listener (server->soup); 249 | if (socket) { 250 | SoupAddress *addr; 251 | g_object_get (socket, SOUP_SOCKET_LOCAL_ADDRESS, &addr, NULL); 252 | g_print ("Now listening on %s:%u\n", 253 | soup_address_get_name (addr), soup_address_get_port (addr)); 254 | g_object_unref (addr); 255 | } 256 | #else 257 | g_print ("Server ready on port %u\n", port); 258 | #endif 259 | } 260 | 261 | static void 262 | aur_server_class_init (AurServerClass * server_class) 263 | { 264 | GObjectClass *gobject_class = (GObjectClass *) (server_class); 265 | 266 | gobject_class->constructed = aur_server_constructed; 267 | gobject_class->dispose = aur_server_dispose; 268 | gobject_class->finalize = aur_server_finalize; 269 | gobject_class->set_property = aur_server_set_property; 270 | gobject_class->get_property = aur_server_get_property; 271 | 272 | g_object_class_install_property (gobject_class, PROP_CONFIG, 273 | g_param_spec_object ("config", "config", 274 | "Aurena service configuration object", 275 | AUR_TYPE_CONFIG, G_PARAM_READWRITE | G_PARAM_CONSTRUCT)); 276 | g_object_class_install_property (gobject_class, PROP_CLOCK, 277 | g_param_spec_object ("clock", "clock", 278 | "clock to synchronise playback", 279 | GST_TYPE_NET_TIME_PROVIDER, G_PARAM_READWRITE)); 280 | } 281 | 282 | static void 283 | aur_server_finalize (GObject * object) 284 | { 285 | AurServer *server = (AurServer *) (object); 286 | g_object_unref (server->soup); 287 | g_hash_table_remove_all (server->resources); 288 | 289 | if (server->config) 290 | g_object_unref (server->config); 291 | 292 | G_OBJECT_CLASS (aur_server_parent_class)->finalize (object); 293 | } 294 | 295 | static void 296 | aur_server_dispose (GObject * object) 297 | { 298 | AurServer *server = (AurServer *) (object); 299 | 300 | soup_server_disconnect (server->soup); 301 | 302 | G_OBJECT_CLASS (aur_server_parent_class)->dispose (object); 303 | } 304 | 305 | static void 306 | aur_server_set_property (GObject * object, guint prop_id, 307 | const GValue * value, GParamSpec * pspec) 308 | { 309 | AurServer *server = (AurServer *) (object); 310 | 311 | switch (prop_id) { 312 | case PROP_CONFIG: 313 | server->config = g_value_dup_object (value); 314 | break; 315 | default: 316 | G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); 317 | break; 318 | } 319 | } 320 | 321 | static void 322 | aur_server_get_property (GObject * object, guint prop_id, 323 | GValue * value, GParamSpec * pspec) 324 | { 325 | AurServer *server = (AurServer *) (object); 326 | 327 | switch (prop_id) { 328 | case PROP_CONFIG: 329 | g_value_set_object (value, server->config); 330 | break; 331 | default: 332 | G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); 333 | break; 334 | } 335 | } 336 | 337 | void 338 | aur_server_start (AurServer * server) 339 | { 340 | gint port; 341 | 342 | g_object_get (server->config, "aur-port", &port, NULL); 343 | soup_server_listen_all (server->soup, port, 0, NULL); 344 | } 345 | 346 | void 347 | aur_server_stop (AurServer * server) 348 | { 349 | soup_server_disconnect (server->soup); 350 | /* some requests may still be in progress but no new 351 | * connections can happen 352 | */ 353 | } 354 | 355 | void 356 | aur_server_set_resource_cb (AurServer * server, 357 | AurHttpResource * (*callback) (AurServer * server, guint resource_id, 358 | void *cb_data), void *userdata) 359 | { 360 | server->get_resource = callback; 361 | server->get_resource_userdata = userdata; 362 | } 363 | 364 | void 365 | aur_server_add_handler (AurServer * server, const gchar * path, 366 | SoupServerCallback callback, gpointer user_data, 367 | GDestroyNotify destroy_notify) 368 | { 369 | soup_server_add_handler (server->soup, path, callback, 370 | user_data, destroy_notify); 371 | } 372 | -------------------------------------------------------------------------------- /src/server/aur-server.h: -------------------------------------------------------------------------------- 1 | /* GStreamer 2 | * Copyright (C) 2012-2014 Jan Schmidt 3 | * 4 | * This library is free software; you can redistribute it and/or 5 | * modify it under the terms of the GNU Library General Public 6 | * License as published by the Free Software Foundation; either 7 | * version 2 of the License, or (at your option) any later version. 8 | * 9 | * This library is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 12 | * Library General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU Library General Public 15 | * License along with this library; if not, write to the 16 | * Free Software Foundation, Inc., 59 Temple Place - Suite 330, 17 | * Boston, MA 02111-1307, USA. 18 | */ 19 | 20 | #ifndef __AUR_SERVER_H__ 21 | #define __AUR_SERVER_H__ 22 | 23 | #include 24 | #include 25 | #include 26 | 27 | #include 28 | 29 | G_BEGIN_DECLS 30 | 31 | #define AUR_TYPE_SERVER (aur_server_get_type ()) 32 | 33 | typedef struct _AurServerClass AurServerClass; 34 | 35 | struct _AurServer 36 | { 37 | GObject parent; 38 | SoupServer *soup; 39 | 40 | GHashTable *resources; 41 | 42 | AurHttpResource *(*get_resource)(AurServer *server, guint resource_id, void *cb_data); 43 | void *get_resource_userdata; 44 | 45 | AurConfig *config; 46 | }; 47 | 48 | struct _AurServerClass 49 | { 50 | GObjectClass parent; 51 | }; 52 | 53 | GType aur_server_get_type(void); 54 | 55 | void aur_server_set_resource_callback (AurServer *server, void *userdata); 56 | void aur_server_start (AurServer *server); 57 | void aur_server_stop (AurServer *server); 58 | void aur_server_set_resource_cb (AurServer *server, 59 | AurHttpResource *(*get_resource)(AurServer *server, guint resource_id, void *cb_data), void *userdata); 60 | 61 | void aur_server_add_handler (AurServer *server, const gchar *path, SoupServerCallback callback, gpointer user_data, GDestroyNotify destroy_notify); 62 | 63 | G_END_DECLS 64 | #endif 65 | -------------------------------------------------------------------------------- /src/simple-client-fullscreen.c: -------------------------------------------------------------------------------- 1 | /* GStreamer 2 | * Copyright (C) 2012 Jan Schmidt 3 | * 4 | * This library is free software; you can redistribute it and/or 5 | * modify it under the terms of the GNU Library General Public 6 | * License as published by the Free Software Foundation; either 7 | * version 2 of the License, or (at your option) any later version. 8 | * 9 | * This library is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 12 | * Library General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU Library General Public 15 | * License along with this library; if not, write to the 16 | * Free Software Foundation, Inc., 59 Temple Place - Suite 330, 17 | * Boston, MA 02111-1307, USA. 18 | */ 19 | 20 | #ifdef HAVE_CONFIG_H 21 | #include "config.h" 22 | #endif 23 | 24 | #include 25 | #include 26 | 27 | #include 28 | #include 29 | #include 30 | 31 | #include 32 | #include 33 | #include // for GDK_WINDOW_XID 34 | 35 | #include 36 | 37 | #include 38 | 39 | #include 40 | 41 | static GMainLoop *ml = NULL; 42 | 43 | 44 | static gboolean 45 | quit_clicked (G_GNUC_UNUSED gpointer instance) 46 | { 47 | g_print ("Exiting...\n"); 48 | g_main_loop_quit (ml); 49 | 50 | return TRUE; 51 | } 52 | 53 | static void 54 | player_created (G_GNUC_UNUSED AurClient *client, GstElement *playbin, 55 | GtkWidget *window) 56 | { 57 | gst_video_overlay_set_window_handle (GST_VIDEO_OVERLAY (playbin), 58 | GDK_WINDOW_XID (gtk_widget_get_window (window))); 59 | 60 | g_print ("Player created\n"); 61 | } 62 | 63 | int 64 | main (int argc, char *argv[]) 65 | { 66 | AurClient *client = NULL; 67 | int ret = 1; 68 | const gchar *server = NULL; 69 | GOptionContext *ctx; 70 | GError *error = NULL; 71 | GtkWidget *window, *eventbox, *button; 72 | 73 | ctx = g_option_context_new ("Aurena fullscreen client"); 74 | 75 | g_option_context_add_group (ctx, gtk_get_option_group(TRUE)); 76 | g_option_context_add_group (ctx, gst_init_get_option_group()); 77 | if (!g_option_context_parse (ctx, &argc, &argv, &error)) { 78 | g_print ("Arguments error: %s", error->message); 79 | return 1; 80 | } 81 | g_option_context_free (ctx); 82 | 83 | if (argc > 1) { 84 | /* Connect directly to the requested server, no avahi */ 85 | server = argv[1]; 86 | } 87 | 88 | avahi_set_allocator (avahi_glib_allocator ()); 89 | 90 | client = aur_client_new (NULL, server, AUR_CLIENT_PLAYER); 91 | if (client == NULL) 92 | goto fail; 93 | 94 | g_object_set (gtk_settings_get_default (), 95 | "gtk-application-prefer-dark-theme", TRUE, NULL); 96 | 97 | window = gtk_window_new (GTK_WINDOW_TOPLEVEL); 98 | gtk_widget_set_redraw_on_allocate (window, FALSE); 99 | gtk_widget_set_app_paintable (window, TRUE); 100 | 101 | g_signal_connect_after (window, "delete-event", G_CALLBACK (quit_clicked), 102 | NULL); 103 | 104 | eventbox = gtk_event_box_new (); 105 | g_object_set (eventbox, 106 | "halign", GTK_ALIGN_END, 107 | "valign", GTK_ALIGN_START, 108 | "expand", FALSE, 109 | NULL); 110 | gtk_widget_set_redraw_on_allocate (eventbox, TRUE); 111 | gtk_widget_set_app_paintable (eventbox, FALSE); 112 | gtk_container_add (GTK_CONTAINER (window), eventbox); 113 | 114 | button = gtk_button_new_from_icon_name ("application-exit", GTK_ICON_SIZE_BUTTON); 115 | gtk_container_add (GTK_CONTAINER (eventbox), button); 116 | g_signal_connect (button, "clicked", G_CALLBACK (quit_clicked), NULL); 117 | 118 | gtk_window_fullscreen (GTK_WINDOW (window)); 119 | gtk_widget_show_all (window); 120 | 121 | g_signal_connect (client, "player-created", G_CALLBACK(player_created), 122 | window); 123 | 124 | 125 | gtk_widget_realize (button); 126 | gdk_window_ensure_native (gtk_widget_get_window (eventbox)); 127 | 128 | ml = g_main_loop_new (NULL, FALSE); 129 | g_main_loop_run (ml); 130 | 131 | ret = 0; 132 | fail: 133 | if (client) 134 | g_object_unref (client); 135 | if (ml) 136 | g_main_loop_unref (ml); 137 | return ret; 138 | } 139 | -------------------------------------------------------------------------------- /src/simple-client.c: -------------------------------------------------------------------------------- 1 | /* GStreamer 2 | * Copyright (C) 2012 Jan Schmidt 3 | * 4 | * This library is free software; you can redistribute it and/or 5 | * modify it under the terms of the GNU Library General Public 6 | * License as published by the Free Software Foundation; either 7 | * version 2 of the License, or (at your option) any later version. 8 | * 9 | * This library is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 12 | * Library General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU Library General Public 15 | * License along with this library; if not, write to the 16 | * Free Software Foundation, Inc., 59 Temple Place - Suite 330, 17 | * Boston, MA 02111-1307, USA. 18 | */ 19 | 20 | #ifdef HAVE_CONFIG_H 21 | #include "config.h" 22 | #endif 23 | 24 | #include 25 | #include 26 | 27 | #include 28 | #include 29 | #include 30 | 31 | #include 32 | 33 | #include 34 | 35 | static GMainLoop *ml = NULL; 36 | 37 | static gboolean 38 | sigint_handler (G_GNUC_UNUSED void *data) 39 | { 40 | g_print ("Exiting...\n"); 41 | g_main_loop_quit (ml); 42 | 43 | return TRUE; 44 | } 45 | 46 | static gboolean 47 | print_position (gpointer user_data) 48 | { 49 | GstElement * player = user_data; 50 | GstFormat format = GST_FORMAT_TIME; 51 | gint64 pos; 52 | 53 | if (gst_element_query_position (player, format, &pos)) { 54 | GstClock * clock; 55 | GstClockTime base_time, stream_time, now; 56 | 57 | if (format != GST_FORMAT_TIME) 58 | goto end; 59 | 60 | clock = gst_element_get_clock (player); 61 | if (!clock) 62 | goto end; 63 | 64 | now = gst_clock_get_time (clock); 65 | gst_object_unref (clock); 66 | base_time = gst_element_get_base_time (player); 67 | stream_time = now - base_time; 68 | 69 | g_print ("Playback position %" GST_TIME_FORMAT " (now %" GST_TIME_FORMAT 70 | " base_time %" GST_TIME_FORMAT " stream_time %" GST_TIME_FORMAT ")\n", 71 | GST_TIME_ARGS (pos), GST_TIME_ARGS (now), GST_TIME_ARGS (base_time), 72 | GST_TIME_ARGS (stream_time)); 73 | } 74 | 75 | end: 76 | return TRUE; 77 | } 78 | 79 | static void 80 | player_disposed (gpointer user_data, 81 | G_GNUC_UNUSED GObject * where_the_object_was) 82 | { 83 | guint timeout = GPOINTER_TO_UINT (user_data); 84 | g_source_remove (timeout); 85 | } 86 | 87 | static void 88 | on_eos_msg (AurClient *client, G_GNUC_UNUSED GstMessage * msg) 89 | { 90 | SoupMessage *soup_msg; 91 | /* FIXME: Next song should all be handled server side */ 92 | char *url = g_strdup_printf ("http://%s:%u/control/next", 93 | client->connected_server, client->connected_port); 94 | 95 | g_print ("Got EOS message\n"); 96 | 97 | soup_msg = soup_message_new ("GET", url); 98 | soup_session_send_message (client->soup, soup_msg); 99 | g_free (url); 100 | } 101 | 102 | static void 103 | player_created (G_GNUC_UNUSED AurClient *client, GstElement * player) 104 | { 105 | GstBus *bus; 106 | guint timeout; 107 | 108 | timeout = g_timeout_add_seconds (1, print_position, player); 109 | g_object_weak_ref (G_OBJECT (player), player_disposed, 110 | GUINT_TO_POINTER (timeout)); 111 | 112 | bus = gst_element_get_bus (GST_ELEMENT (client->player)); 113 | g_signal_connect_swapped (bus, "message::eos", G_CALLBACK (on_eos_msg), 114 | client); 115 | gst_object_unref (bus); 116 | } 117 | 118 | int 119 | main (int argc, char *argv[]) 120 | { 121 | AurClient *client = NULL; 122 | int ret = 1; 123 | const gchar *server = NULL; 124 | 125 | gst_init (&argc, &argv); 126 | 127 | if (argc > 1) { 128 | /* Connect directly to the requested server, no avahi */ 129 | server = argv[1]; 130 | } 131 | 132 | avahi_set_allocator (avahi_glib_allocator ()); 133 | 134 | g_unix_signal_add (SIGINT, sigint_handler, NULL); 135 | 136 | client = aur_client_new (NULL, server, AUR_CLIENT_PLAYER); 137 | if (client == NULL) 138 | goto fail; 139 | 140 | g_signal_connect (client, "player-created", G_CALLBACK (player_created), 141 | NULL); 142 | 143 | ml = g_main_loop_new (NULL, FALSE); 144 | g_main_loop_run (ml); 145 | 146 | ret = 0; 147 | fail: 148 | if (client) 149 | g_object_unref (client); 150 | if (ml) 151 | g_main_loop_unref (ml); 152 | return ret; 153 | } 154 | -------------------------------------------------------------------------------- /test/.gitignore: -------------------------------------------------------------------------------- 1 | clock-receiver 2 | clock-server 3 | clock-bouncer 4 | -------------------------------------------------------------------------------- /test/Makefile.am: -------------------------------------------------------------------------------- 1 | noinst_PROGRAMS = clock-server clock-receiver clock-bouncer 2 | 3 | clock_server_CPPFLAGS = -I$(top_srcdir) $(GST_CFLAGS) $(AUR_COMMON_CFLAGS) $(EXTRA_CFLAGS) 4 | clock_server_LDADD = $(GST_LIBS) 5 | clock_server_SOURCES = clock-server.c 6 | 7 | clock_receiver_CPPFLAGS = -I$(top_srcdir) $(GST_CFLAGS) $(AUR_COMMON_CFLAGS) $(EXTRA_CFLAGS) 8 | clock_receiver_LDADD = $(GST_LIBS) 9 | clock_receiver_SOURCES = clock-receiver.c 10 | 11 | clock_bouncer_CPPFLAGS = -I$(top_srcdir) $(GST_CFLAGS) $(AUR_COMMON_CFLAGS) $(EXTRA_CFLAGS) 12 | clock_bouncer_LDADD = $(AUR_COMMON_LIBS) 13 | clock_bouncer_SOURCES = clock-bouncer.c 14 | -------------------------------------------------------------------------------- /test/clock-bouncer.c: -------------------------------------------------------------------------------- 1 | #ifdef CONFIG_H 2 | #include "config.h" 3 | #endif 4 | 5 | #include 6 | #include 7 | #include 8 | #include 9 | 10 | #ifdef G_OS_UNIX 11 | #include 12 | #include 13 | #endif 14 | 15 | typedef struct PortInfo 16 | { 17 | GSocket *socket; /* socket we used to send packet to the master */ 18 | GSocketAddress *src_address; /* Where to return the packet */ 19 | GInetAddress *inet_addr; /* The client's inet_addr, from src_address */ 20 | gint port; /* The client's port, from src_address */ 21 | } PortInfo; 22 | 23 | GMainLoop *loop; 24 | GSocket *masterSocket; 25 | GSocketAddress *serverAddress; 26 | GList *ports = NULL; 27 | 28 | static void listen_on_socket(gint listenPort, GSourceFunc handler, gpointer user_data); 29 | static void send_packet_to_master(GSocketAddress *src_address, gchar *buf); 30 | 31 | static gboolean 32 | intr_handler (G_GNUC_UNUSED gpointer user_data) 33 | { 34 | g_print("Exiting.\n"); 35 | g_main_loop_quit(loop); 36 | return FALSE; 37 | } 38 | 39 | static gboolean 40 | receive_clock_packet(GSocket *socket, G_GNUC_UNUSED GIOCondition condition, 41 | gpointer user_data) 42 | { 43 | gchar buffer[GST_NET_TIME_PACKET_SIZE]; 44 | GSocketAddress *src_address; 45 | gssize ret; 46 | 47 | ret = g_socket_receive_from (socket, &src_address, buffer, 48 | GST_NET_TIME_PACKET_SIZE, NULL, NULL); 49 | if (ret < GST_NET_TIME_PACKET_SIZE) { 50 | g_print ("Packet too small: %" G_GSSIZE_FORMAT "\n", ret); 51 | return TRUE; 52 | } 53 | 54 | if (user_data == NULL) { 55 | send_packet_to_master(src_address, buffer); 56 | } 57 | else { 58 | /* Return the reply to the client */ 59 | PortInfo *portinfo = (PortInfo *)(user_data); 60 | g_socket_send_to (masterSocket, portinfo->src_address, (const gchar *) buffer, 61 | GST_NET_TIME_PACKET_SIZE, NULL, NULL); 62 | } 63 | return TRUE; 64 | } 65 | 66 | 67 | static void 68 | send_packet_to_master(GSocketAddress *src_address, gchar *buf) 69 | { 70 | PortInfo *portinfo = NULL; 71 | GList *cur; 72 | GInetAddress *inet_addr; 73 | gint inet_port; 74 | 75 | /* Locate the socket for this source address */ 76 | inet_port = g_inet_socket_address_get_port (G_INET_SOCKET_ADDRESS (src_address)); 77 | inet_addr = 78 | g_inet_socket_address_get_address (G_INET_SOCKET_ADDRESS (src_address)); 79 | cur = g_list_first(ports); 80 | while (cur != NULL) { 81 | PortInfo *cur_port = (PortInfo *)(cur->data); 82 | if (g_inet_address_equal (inet_addr, cur_port->inet_addr) && inet_port == cur_port->port) { 83 | portinfo = cur_port; 84 | break; 85 | } 86 | cur = g_list_next(cur); 87 | } 88 | 89 | /* Otherwise, create one */ 90 | if (portinfo == NULL) { 91 | gchar *tmp; 92 | 93 | portinfo = g_new0(PortInfo, 1); 94 | portinfo->src_address = src_address; 95 | portinfo->inet_addr = inet_addr; 96 | portinfo->port = inet_port; 97 | 98 | tmp = g_inet_address_to_string(inet_addr); 99 | g_print ("Packet from new client %s:%d\n", tmp, inet_port); 100 | g_free (tmp); 101 | 102 | listen_on_socket(0, (GSourceFunc)(receive_clock_packet), portinfo); 103 | ports = g_list_prepend(ports, portinfo); 104 | } 105 | 106 | /* And send the packet to the master clock provider */ 107 | g_socket_send_to (masterSocket, serverAddress, (const gchar *) buf, 108 | GST_NET_TIME_PACKET_SIZE, NULL, NULL); 109 | } 110 | 111 | static void 112 | listen_on_socket(gint listenPort, GSourceFunc handler, gpointer user_data) 113 | { 114 | GInetAddress *localAddress; 115 | GSocketAddress *localSocketAddress; 116 | GSource *source; 117 | 118 | localAddress = g_inet_address_new_from_string("0.0.0.0"); 119 | localSocketAddress = g_inet_socket_address_new(localAddress, listenPort); 120 | masterSocket = g_socket_new(G_SOCKET_FAMILY_IPV4, G_SOCKET_TYPE_DATAGRAM, G_SOCKET_PROTOCOL_UDP, NULL); 121 | g_socket_bind (masterSocket, localSocketAddress, FALSE, NULL); 122 | 123 | source = g_socket_create_source (masterSocket, G_IO_IN, NULL); 124 | g_source_set_callback (source, handler, user_data, NULL); 125 | g_source_attach(source, NULL); 126 | 127 | g_socket_listen (masterSocket, NULL); 128 | } 129 | 130 | static GInetAddress * 131 | make_inet_address(const gchar *hostname) 132 | { 133 | GResolver *r; 134 | GList *result; 135 | GInetAddress *addr; 136 | r = g_resolver_get_default(); 137 | result = g_resolver_lookup_by_name(r, hostname, NULL, NULL); 138 | g_object_unref(r); 139 | if (result == NULL) 140 | return NULL; 141 | addr = g_object_ref (result->data); 142 | g_resolver_free_addresses(result); 143 | return addr; 144 | } 145 | 146 | int 147 | main(int argc, char **argv) 148 | { 149 | guint signal_watch_id; 150 | gchar *server; 151 | gint local_clock_port, server_clock_port; 152 | GInetAddress *server_inet_addr; 153 | 154 | if (argc < 4) { 155 | g_print ("Usage %s \n Listen on port and forward to :\n", argv[0]); 156 | return 1; 157 | } 158 | 159 | local_clock_port = atoi(argv[1]); 160 | server = argv[2]; 161 | server_clock_port = atoi(argv[3]); 162 | 163 | #ifdef G_OS_UNIX 164 | signal_watch_id = 165 | g_unix_signal_add (SIGINT, (GSourceFunc) intr_handler, NULL); 166 | #endif 167 | loop = g_main_loop_new(NULL, FALSE); 168 | 169 | listen_on_socket(local_clock_port, (GSourceFunc)(receive_clock_packet), NULL); 170 | server_inet_addr = make_inet_address(server); 171 | if (server_inet_addr == NULL) { 172 | g_print ("Failed to resolve hostname %s\n", server); 173 | goto done; 174 | } 175 | serverAddress = g_inet_socket_address_new(server_inet_addr, server_clock_port); 176 | g_object_unref(server_inet_addr); 177 | 178 | g_main_loop_run(loop); 179 | 180 | done: 181 | g_source_remove (signal_watch_id); 182 | g_main_loop_unref (loop); 183 | 184 | return 0; 185 | } 186 | -------------------------------------------------------------------------------- /test/clock-receiver.c: -------------------------------------------------------------------------------- 1 | #ifdef CONFIG_H 2 | #include "config.h" 3 | #endif 4 | 5 | #include 6 | #include 7 | #include 8 | 9 | #ifdef G_OS_UNIX 10 | #include 11 | #include 12 | #endif 13 | 14 | GMainLoop *loop; 15 | GstClock *sys_clock; 16 | 17 | static gboolean 18 | intr_handler (G_GNUC_UNUSED gpointer user_data) 19 | { 20 | g_print("Exiting.\n"); 21 | g_main_loop_quit(loop); 22 | return TRUE; 23 | } 24 | 25 | GstClockTime last_internal_time = -1, last_external_time = -1, last_rate_num = -1, last_rate_den = -1; 26 | 27 | gboolean 28 | clock_poll (GstClock *net_clock) 29 | { 30 | GstClockTime cur_sys_time = gst_clock_get_time (sys_clock); 31 | GstClockTime cur_net_time = gst_clock_get_time (net_clock); 32 | GstClockTime internal_time, external_time, rate_num, rate_den; 33 | 34 | gst_clock_get_calibration(net_clock, &internal_time, &external_time, &rate_num, &rate_den); 35 | 36 | if (last_internal_time != internal_time || 37 | last_external_time != external_time || 38 | last_rate_num != rate_num || 39 | last_rate_den != rate_den) 40 | { 41 | g_print("%" G_GUINT64_FORMAT "\t%" G_GUINT64_FORMAT "\t%" GST_TIME_FORMAT 42 | "\t%" G_GINT64_FORMAT "\t%" G_GUINT64_FORMAT 43 | "\t%" G_GUINT64_FORMAT "\t%" G_GUINT64_FORMAT "\t%g\n", 44 | cur_sys_time, cur_net_time, GST_TIME_ARGS(cur_net_time), 45 | GST_CLOCK_DIFF (cur_net_time, cur_sys_time), 46 | internal_time, rate_num, rate_den, (gdouble)(rate_num) / rate_den); 47 | last_internal_time = internal_time; 48 | last_external_time = external_time; 49 | last_rate_num = rate_num; 50 | last_rate_den = rate_den; 51 | } 52 | return TRUE; 53 | } 54 | 55 | int 56 | main(int argc, char **argv) 57 | { 58 | guint signal_watch_id; 59 | guint timeout_id; 60 | GstClock *net_clock; 61 | gchar *server; 62 | gint clock_port; 63 | 64 | gst_init(&argc, &argv); 65 | 66 | if (argc < 3) { 67 | g_print ("Usage %s \n", argv[0]); 68 | return 1; 69 | } 70 | 71 | server = argv[1]; 72 | clock_port = atoi(argv[2]); 73 | 74 | sys_clock = gst_system_clock_obtain (); 75 | net_clock = gst_net_client_clock_new ("net_clock", server, 76 | clock_port, 0); 77 | if (net_clock == NULL) { 78 | g_print ("Failed to create net clock client for %s:%d\n", 79 | server, clock_port); 80 | return 1; 81 | } 82 | 83 | #ifdef G_OS_UNIX 84 | signal_watch_id = 85 | g_unix_signal_add (SIGINT, (GSourceFunc) intr_handler, NULL); 86 | #endif 87 | 88 | loop = g_main_loop_new(NULL, FALSE); 89 | timeout_id = g_timeout_add(100, (GSourceFunc)clock_poll, net_clock); 90 | 91 | //g_print("Ready - polling clock server at %s:%d\n", server, clock_port); 92 | g_print("LocalSysTime\tRemoteTime\tRemoteTimeStr\tDiff\n"); 93 | g_main_loop_run(loop); 94 | 95 | gst_object_unref (net_clock); 96 | gst_object_unref (sys_clock); 97 | g_source_remove (signal_watch_id); 98 | g_source_remove (timeout_id); 99 | g_main_loop_unref (loop); 100 | 101 | return 0; 102 | } 103 | -------------------------------------------------------------------------------- /test/clock-server.c: -------------------------------------------------------------------------------- 1 | #ifdef CONFIG_H 2 | #include "config.h" 3 | #endif 4 | 5 | #include 6 | #include 7 | #include 8 | 9 | #ifdef G_OS_UNIX 10 | #include 11 | #include 12 | #endif 13 | 14 | GMainLoop *loop; 15 | 16 | static gboolean 17 | intr_handler (G_GNUC_UNUSED gpointer user_data) 18 | { 19 | g_print("Exiting.\n"); 20 | g_main_loop_quit(loop); 21 | return TRUE; 22 | } 23 | 24 | int 25 | main(int argc, char **argv) 26 | { 27 | guint signal_watch_id; 28 | GstClock *clock; 29 | GstNetTimeProvider *net_clock; 30 | gint clock_port; 31 | 32 | gst_init(&argc, &argv); 33 | 34 | clock = gst_system_clock_obtain (); 35 | net_clock = gst_net_time_provider_new (clock, NULL, 0); 36 | g_object_get (net_clock, "port", &clock_port, NULL); 37 | gst_object_unref (clock); 38 | 39 | #ifdef G_OS_UNIX 40 | signal_watch_id = 41 | g_unix_signal_add (SIGINT, (GSourceFunc) intr_handler, NULL); 42 | #endif 43 | 44 | loop = g_main_loop_new(NULL, FALSE); 45 | 46 | g_print("Net clock provider ready on port %d.\n", clock_port); 47 | g_main_loop_run(loop); 48 | 49 | gst_object_unref (net_clock); 50 | g_source_remove (signal_watch_id); 51 | 52 | return 0; 53 | } 54 | -------------------------------------------------------------------------------- /test/play-test.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | import sys 3 | 4 | import gobject 5 | gobject.threads_init() 6 | 7 | import glib 8 | 9 | import pygst 10 | pygst.require('0.10') 11 | import gst 12 | 13 | import json 14 | import urllib 15 | import socket 16 | 17 | ml = None 18 | def rtp_child_added (src, elem): 19 | if elem.get_name().startswith("rtpbin"): 20 | print "Setting ntp-sync" 21 | elem.set_property("ntp-sync", True) 22 | elem.set_property("use-pipeline-clock", True) 23 | 24 | def handle_src_element(pb, src): 25 | if src.get_name().startswith("rtsp"): 26 | src.set_property("buffer-mode", 0) 27 | src.connect ("element-added", rtp_child_added) 28 | 29 | def on_message(bus, message): 30 | global ml 31 | t = message.type 32 | if t == gst.MESSAGE_ERROR: 33 | err, debug = message.parse_error() 34 | print "Error: %s" % err, debug 35 | ml.quit() 36 | elif t == gst.MESSAGE_EOS: 37 | print "EOS" 38 | ml.quit() 39 | 40 | def clock_update(clock, base_time): 41 | t= clock.get_time() 42 | print "Now %d (%d)" % (t, t - base_time) 43 | return True 44 | 45 | def main(args): 46 | _, server = args 47 | 48 | u = urllib.urlopen('http://' + server + ':5457/control') 49 | print u.read() 50 | 51 | ctx = json.load(u) 52 | port = ctx['clock-port'] 53 | base_time = ctx['base-time'] 54 | media_uri = '%s://%s:%d%s' % (ctx['resource-protocol'], server, ctx['resource-port'], ctx['resource-path']) 55 | 56 | print ctx 57 | 58 | # make a clock slaving to the network 59 | ip = socket.gethostbyname(server) 60 | clock = gst.NetClientClock(None, ip, port, ctx['current-time']) 61 | 62 | # make the pipeline 63 | pipeline = gst.parse_launch('playbin2 name=pb'); 64 | pipeline.set_property("uri", media_uri) # uri interface 65 | 66 | pipeline.connect ("source-setup", handle_src_element) 67 | 68 | # disable the pipeline's management of base_time -- we're going 69 | # to set it ourselves. 70 | pipeline.set_new_stream_time(gst.CLOCK_TIME_NONE) 71 | 72 | # use it in the pipeline 73 | pipeline.set_base_time(base_time) 74 | pipeline.use_clock(clock) 75 | 76 | # now we go :) 77 | print "Starting playback of %s with base_time %d\n" % (media_uri, base_time) 78 | pipeline.set_state(gst.STATE_PLAYING) 79 | 80 | bus = pipeline.get_bus() 81 | bus.enable_sync_message_emission() 82 | bus.add_signal_watch() 83 | bus.connect('message', on_message) 84 | 85 | glib.timeout_add_seconds(5000, clock_update, clock, base_time) 86 | # wait until things stop 87 | global ml 88 | try: 89 | ml = glib.MainLoop() 90 | ml.run() 91 | except: 92 | pass 93 | 94 | pipeline.set_state(gst.STATE_NULL) 95 | 96 | if __name__ == '__main__': 97 | main(sys.argv) 98 | --------------------------------------------------------------------------------