dhun 0.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
data/ext/extconf.rb ADDED
@@ -0,0 +1,15 @@
1
+ # Loads mkmf which is used to make makefiles for Ruby extensions
2
+ require 'mkmf'
3
+
4
+ # Give it a name
5
+ extension_name = 'dhun_ext'
6
+
7
+ # The destination
8
+ dir_config(extension_name)
9
+
10
+ # Config changes
11
+ $LDFLAGS << " -framework AudioToolbox -framework CoreServices"
12
+ $CFLAGS << " -std=c99"
13
+
14
+ # Do the work
15
+ create_makefile(extension_name)
data/ext/player.c ADDED
@@ -0,0 +1,187 @@
1
+ #include "dhun.h"
2
+
3
+ AQPlayerState aqData;
4
+
5
+ static void HandleOutputBuffer (void *aqData,
6
+ AudioQueueRef inAQ,
7
+ AudioQueueBufferRef inBuffer) {
8
+ AQPlayerState *pAqData = (AQPlayerState *) aqData;
9
+
10
+ if (pAqData->mIsRunning == 0) return;
11
+ UInt32 numBytesReadFromFile;
12
+ UInt32 numPackets = pAqData->mNumPacketsToRead;
13
+
14
+ AudioFileReadPackets (pAqData->mAudioFile,
15
+ false,
16
+ &numBytesReadFromFile,
17
+ pAqData->mPacketDescs,
18
+ pAqData->mCurrentPacket,
19
+ &numPackets,
20
+ inBuffer->mAudioData);
21
+ if (numPackets > 0) {
22
+ inBuffer->mAudioDataByteSize = numBytesReadFromFile;
23
+ AudioQueueEnqueueBuffer (pAqData->mQueue,
24
+ inBuffer,
25
+ (pAqData->mPacketDescs ? numPackets : 0),
26
+ pAqData->mPacketDescs);
27
+ pAqData->mCurrentPacket += numPackets;
28
+ } else {
29
+ AudioQueueStop (pAqData->mQueue,
30
+ false);
31
+ //printf("Play Stopped!\n");
32
+ pAqData->mIsRunning = false;
33
+ }
34
+ }
35
+
36
+ void DeriveBufferSize (AudioStreamBasicDescription *ASBDesc,
37
+ UInt32 maxPacketSize,
38
+ Float64 seconds,
39
+ UInt32 *outBufferSize,
40
+ UInt32 *outNumPacketsToRead) {
41
+ static const int maxBufferSize = 0x50000;
42
+ static const int minBufferSize = 0x4000;
43
+
44
+ if (ASBDesc->mFramesPerPacket != 0) {
45
+ Float64 numPacketsForTime =
46
+ ASBDesc->mSampleRate / ASBDesc->mFramesPerPacket * seconds;
47
+
48
+ *outBufferSize = numPacketsForTime * maxPacketSize;
49
+ } else {
50
+ *outBufferSize =
51
+ maxBufferSize > maxPacketSize ?
52
+ maxBufferSize : maxPacketSize;
53
+ }
54
+
55
+ if (
56
+ *outBufferSize > maxBufferSize &&
57
+ *outBufferSize > maxPacketSize
58
+ )
59
+ *outBufferSize = maxBufferSize;
60
+ else {
61
+ if (*outBufferSize < minBufferSize)
62
+ *outBufferSize = minBufferSize;
63
+ }
64
+
65
+ *outNumPacketsToRead = *outBufferSize / maxPacketSize;
66
+ }
67
+
68
+ void playFile(const char* filePath) {
69
+
70
+ CFURLRef audioFileURL = CFURLCreateFromFileSystemRepresentation(NULL,
71
+ (UInt8*) filePath,
72
+ strlen (filePath),
73
+ false);
74
+
75
+
76
+ OSStatus result = AudioFileOpenURL(audioFileURL,
77
+ fsRdPerm,
78
+ 0,
79
+ &aqData.mAudioFile);
80
+
81
+ CFRelease (audioFileURL);
82
+
83
+ UInt32 dataFormatSize = sizeof (aqData.mDataFormat);
84
+
85
+ AudioFileGetProperty(aqData.mAudioFile,
86
+ kAudioFilePropertyDataFormat,
87
+ &dataFormatSize,
88
+ &aqData.mDataFormat);
89
+
90
+ AudioQueueNewOutput(&aqData.mDataFormat,
91
+ HandleOutputBuffer,
92
+ &aqData,
93
+ CFRunLoopGetCurrent(),
94
+ kCFRunLoopCommonModes,
95
+ 0,
96
+ &aqData.mQueue);
97
+
98
+ UInt32 maxPacketSize;
99
+ UInt32 propertySize = sizeof (maxPacketSize);
100
+ AudioFileGetProperty(aqData.mAudioFile,
101
+ kAudioFilePropertyPacketSizeUpperBound,
102
+ &propertySize,
103
+ &maxPacketSize);
104
+
105
+
106
+ DeriveBufferSize(&aqData.mDataFormat,
107
+ maxPacketSize,
108
+ 0.5,
109
+ &aqData.bufferByteSize,
110
+ &aqData.mNumPacketsToRead);
111
+
112
+ bool isFormatVBR = (aqData.mDataFormat.mBytesPerPacket == 0 ||
113
+ aqData.mDataFormat.mFramesPerPacket == 0);
114
+
115
+ if (isFormatVBR) {
116
+ // LOG("%s\n","VBR");
117
+ aqData.mPacketDescs =
118
+ (AudioStreamPacketDescription*)
119
+ malloc (aqData.mNumPacketsToRead * sizeof (AudioStreamPacketDescription));
120
+ } else {
121
+ aqData.mPacketDescs = NULL;
122
+ }
123
+
124
+ UInt32 cookieSize = sizeof (UInt32);
125
+ bool couldNotGetProperty =
126
+ AudioFileGetPropertyInfo (aqData.mAudioFile,
127
+ kAudioFilePropertyMagicCookieData,
128
+ &cookieSize,
129
+ NULL);
130
+
131
+ if (!couldNotGetProperty && cookieSize) {
132
+ char* magicCookie = (char *) malloc (cookieSize);
133
+
134
+ AudioFileGetProperty (aqData.mAudioFile,
135
+ kAudioFilePropertyMagicCookieData,
136
+ &cookieSize,
137
+ magicCookie);
138
+
139
+ AudioQueueSetProperty (aqData.mQueue,
140
+ kAudioQueueProperty_MagicCookie,
141
+ magicCookie,
142
+ cookieSize);
143
+
144
+ free (magicCookie);
145
+ }
146
+
147
+ aqData.mCurrentPacket = 0;
148
+ aqData.mIsRunning = true;
149
+
150
+ //LOG("%d\n", aqData.mNumPacketsToRead);
151
+ for (int i = 0; i < kNumberBuffers; ++i) {
152
+ AudioQueueAllocateBuffer (aqData.mQueue,
153
+ aqData.bufferByteSize,
154
+ &aqData.mBuffers[i]);
155
+
156
+ HandleOutputBuffer (&aqData,
157
+ aqData.mQueue,
158
+ aqData.mBuffers[i]);
159
+ }
160
+
161
+ Float32 gain = 1.0;
162
+ // Optionally, allow user to override gain setting here
163
+ AudioQueueSetParameter (aqData.mQueue,
164
+ kAudioQueueParam_Volume,
165
+ gain);
166
+
167
+
168
+ //LOG("%s\n","Starting play");
169
+
170
+
171
+ // IMPORTANT NOTE : This value must be set
172
+ // Before the call to HandleOutputBuffer
173
+ //a qData.mIsRunning = true;
174
+
175
+ AudioQueueStart (aqData.mQueue,
176
+ NULL);
177
+
178
+ }
179
+
180
+
181
+ void free_aqData() {
182
+ AudioQueueDispose (aqData.mQueue, true);
183
+
184
+ AudioFileClose (aqData.mAudioFile);
185
+
186
+ free (aqData.mPacketDescs);
187
+ }
data/ext/query.c ADDED
@@ -0,0 +1,102 @@
1
+ #include "dhun.h"
2
+
3
+ SearchResults queryResults;
4
+
5
+ void notificationCallback(CFNotificationCenterRef center,
6
+ void *observer,
7
+ CFStringRef name,
8
+ const void *object,
9
+ CFDictionaryRef userInfo) {
10
+ //CFDictionaryRef attributes;
11
+ //CFArrayRef attributeNames;
12
+ CFTypeRef attrValue = NULL;
13
+ CFIndex idx, count;
14
+ MDItemRef itemRef = NULL;
15
+ MDQueryRef queryRef = (MDQueryRef)object;
16
+ CFStringRef attrName = NULL;
17
+ CFStringEncoding encoding = CFStringGetSystemEncoding();
18
+
19
+ if (CFStringCompare(name, kMDQueryDidFinishNotification, 0)
20
+ == kCFCompareEqualTo) { // gathered results
21
+ // disable updates, process results, and reenable updates
22
+ MDQueryDisableUpdates(queryRef);
23
+ count = MDQueryGetResultCount(queryRef);
24
+ if (count > 0) {
25
+ queryResults.size=count;
26
+ queryResults.files = (char**) malloc(count*sizeof(char*));
27
+ for (idx = 0; idx < count; idx++) {
28
+ itemRef = (MDItemRef)MDQueryGetResultAtIndex(queryRef, idx);
29
+ //attributeNames = MDItemCopyAttributeNames(itemRef);
30
+ //attributes = MDItemCopyAttributes(itemRef, attributeNames);
31
+ attrName = CFStringCreateWithCString(NULL,
32
+ "kMDItemPath", encoding);
33
+ attrValue = MDItemCopyAttribute(itemRef, attrName);
34
+ const char* convertedString = CFStringGetCStringPtr((CFStringRef)attrValue, encoding);
35
+ queryResults.files[idx] = malloc(strlen(convertedString)+1);
36
+ strcpy(queryResults.files[idx],convertedString);
37
+ //CFShow(attrValue);
38
+ //CFRelease(attributes);
39
+ //CFRelease(attributeNames);
40
+ }
41
+ //printf("%ld results total\n", count);
42
+ }
43
+ MDQueryEnableUpdates(queryRef);
44
+ } else if (CFStringCompare(name, kMDQueryDidUpdateNotification, 0)
45
+ == kCFCompareEqualTo) { // live update
46
+ CFShow(name), CFShow(object), CFShow(userInfo);
47
+ }
48
+ // ignore kMDQueryProgressNotification
49
+ }
50
+
51
+ int getFilesForQuery(const char* queryStr)
52
+ {
53
+ int i;
54
+ CFStringRef rawQuery = NULL;
55
+ MDQueryRef queryRef;
56
+ Boolean result;
57
+ CFNotificationCenterRef localCenter;
58
+ MDQueryBatchingParams batchingParams;
59
+ //char* query;
60
+ //asprintf(&query,QUERY_TEMPLATE,queryStr,queryStr,queryStr);
61
+ //printf("Querying for %s\n", query);
62
+ rawQuery = CFStringCreateWithCString(kCFAllocatorDefault,
63
+ queryStr,
64
+ CFStringGetSystemEncoding());
65
+
66
+ queryRef = MDQueryCreate(kCFAllocatorDefault, rawQuery, NULL, NULL);
67
+ if (queryRef == NULL)
68
+ goto out;
69
+
70
+ if (!(localCenter = CFNotificationCenterGetLocalCenter())) {
71
+ fprintf(stderr, "failed to access local notification center\n");
72
+ goto out;
73
+ }
74
+
75
+ CFNotificationCenterAddObserver(localCenter, // process-local center
76
+ NULL, // observer
77
+ notificationCallback, // to process query finish/update notifications
78
+ NULL, // observe all notifications
79
+ (void *)queryRef, // observe notifications for this object
80
+ CFNotificationSuspensionBehaviorDeliverImmediately);
81
+
82
+ // maximum number of results that can accumulate and the maximum number
83
+ // of milliseconds that can pass before various notifications are sent
84
+ batchingParams.first_max_num = 1000; // first progress notification
85
+ batchingParams.first_max_ms = 1000;
86
+ batchingParams.progress_max_num = 1000; // additional progress notifications
87
+ batchingParams.progress_max_ms = 1000;
88
+ batchingParams.update_max_num = 1; // update notification
89
+ batchingParams.update_max_ms = 1000;
90
+ MDQuerySetBatchingParameters(queryRef, batchingParams);
91
+
92
+ // go execute the query
93
+ MDQueryExecute(queryRef, kMDQuerySynchronous);
94
+
95
+ out:
96
+ CFRelease(rawQuery);
97
+ if (queryRef)
98
+ CFRelease(queryRef);
99
+
100
+ // free(query);
101
+ return 0;
102
+ }
data/lib/dhun.rb ADDED
@@ -0,0 +1,13 @@
1
+ module Dhun
2
+ VERSION = '0.5.0'
3
+
4
+ autoload :Runner, 'dhun/runner'
5
+ autoload :Controller, 'dhun/controller'
6
+ autoload :Server, 'dhun/server'
7
+ autoload :DhunServer, 'dhun/dhun_server'
8
+ autoload :DhunClient, 'dhun/dhun_client'
9
+ autoload :Handler, 'dhun/handler'
10
+ autoload :Player, 'dhun/player'
11
+ autoload :Query, 'dhun/query'
12
+ autoload :Result, 'dhun/result'
13
+ end
@@ -0,0 +1,15 @@
1
+ require 'json'
2
+ module Dhun
3
+ class Command
4
+ attr_reader :commands, :arguments
5
+
6
+ def initialize(command,arguments)
7
+ @command = command
8
+ @arguments = arguments
9
+ end
10
+
11
+ def to_json
12
+ { "command" => @command, "arguments" => @arguments }.to_json
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,116 @@
1
+ require 'json'
2
+ module Dhun
3
+ class Controller
4
+
5
+ attr_accessor :options
6
+
7
+ def initialize(options)
8
+ @options = options
9
+ end
10
+
11
+ def start
12
+ server = Server.new(@options)
13
+ if DhunClient.is_dhun_server_running?(@options[:socket])
14
+ puts "Dhun is already running"
15
+ else
16
+ server.start
17
+ end
18
+ end
19
+
20
+ def stop
21
+ send_command("stop")
22
+ end
23
+
24
+ def query(*args)
25
+ q = Query.new(args.join(' '))
26
+ if q.is_valid?
27
+ files = q.execute_spotlight_query
28
+ puts(files.empty? ? "No Results Found" : "#{files.size} Results\n" + files.join("\n"))
29
+ else
30
+ puts "Invalid Query Syntax. Run dhun -h to see right syntax"
31
+ end
32
+ end
33
+
34
+ def play(*args)
35
+ resp = get_json_response("play", args)
36
+ return unless resp
37
+ # Process response
38
+ case resp.success?
39
+ when true
40
+ puts resp[:message]
41
+ # Print list of files
42
+ print_list resp[:files]
43
+ else
44
+ puts resp[:message]
45
+ end
46
+ end
47
+
48
+ def enqueue(*args)
49
+ resp = get_json_response("enqueue",args)
50
+ return unless resp
51
+ # Process response
52
+ case resp.success?
53
+ when true
54
+ puts resp[:message]
55
+ # Print list of files
56
+ print_list resp[:files]
57
+ else
58
+ puts resp[:message]
59
+ end
60
+ end
61
+
62
+ def status
63
+ resp = get_json_response("status")
64
+ return unless resp
65
+ puts resp[:message]
66
+ if resp.success?
67
+ now_playing = resp[:now_playing]
68
+ queue = resp[:queue]
69
+ puts "Now playing #{now_playing}" if now_playing
70
+ if queue.empty?
71
+ puts "Queue is empty"
72
+ else
73
+ print_list(queue)
74
+ end
75
+ end
76
+ end
77
+
78
+ def next(*args)
79
+ resp = get_json_response("next")
80
+ puts resp[:message] if resp
81
+ end
82
+
83
+ def pause
84
+ resp = get_json_response("pause")
85
+ puts resp[:message] if resp
86
+ end
87
+
88
+ def resume
89
+ resp = get_json_response("resume")
90
+ puts resp[:message] if resp
91
+ end
92
+
93
+
94
+ protected
95
+ def send_command(command,arguments=[])
96
+ cmd = { "command" => command, "arguments" => arguments }.to_json
97
+ client = DhunClient.new(@options)
98
+ resp = client.send(cmd)
99
+ end
100
+
101
+ def get_json_response(command,*args)
102
+ begin
103
+ resp = send_command(command,args)
104
+ return Result.from_json_str(resp)
105
+ rescue
106
+ puts "Invalid Response From Server"
107
+ puts $!
108
+ return nil
109
+ end
110
+ end
111
+
112
+ def print_list(list)
113
+ list.each { |item| puts item }
114
+ end
115
+ end
116
+ end