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/LICENSE.txt +22 -0
- data/README.md +32 -0
- data/Rakefile +83 -0
- data/TODO.md +19 -0
- data/bin/dhun +7 -0
- data/ext/Makefile +157 -0
- data/ext/dhun.h +37 -0
- data/ext/dhun_ext.c +84 -0
- data/ext/extconf.rb +15 -0
- data/ext/player.c +187 -0
- data/ext/query.c +102 -0
- data/lib/dhun.rb +13 -0
- data/lib/dhun/command.rb +15 -0
- data/lib/dhun/controller.rb +116 -0
- data/lib/dhun/dhun_client.rb +30 -0
- data/lib/dhun/dhun_server.rb +43 -0
- data/lib/dhun/handler.rb +80 -0
- data/lib/dhun/player.rb +66 -0
- data/lib/dhun/query.rb +44 -0
- data/lib/dhun/result.rb +32 -0
- data/lib/dhun/runner.rb +87 -0
- data/lib/dhun/server.rb +39 -0
- metadata +76 -0
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
|
data/lib/dhun/command.rb
ADDED
@@ -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
|