1 /* 2 * Copyright 2008, The Android Open Source Project 3 * 4 * Redistribution and use in source and binary forms, with or without 5 * modification, are permitted provided that the following conditions 6 * are met: 7 * * Redistributions of source code must retain the above copyright 8 * notice, this list of conditions and the following disclaimer. 9 * * Redistributions in binary form must reproduce the above copyright 10 * notice, this list of conditions and the following disclaimer in the 11 * documentation and/or other materials provided with the distribution. 12 * 13 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS ``AS IS'' AND ANY 14 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 15 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 16 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE COMPUTER, INC. OR 17 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, 18 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, 19 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 20 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY 21 * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 22 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 23 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 24 */ 25 26 #include "AudioPlugin.h" 27 28 #include <fcntl.h> 29 #include <sys/stat.h> 30 #include <sys/time.h> 31 #include <time.h> 32 #include <math.h> 33 #include <string.h> 34 35 extern NPNetscapeFuncs* browser; 36 extern ANPLogInterfaceV0 gLogI; 37 extern ANPCanvasInterfaceV0 gCanvasI; 38 extern ANPPaintInterfaceV0 gPaintI; 39 extern ANPAudioTrackInterfaceV0 gSoundI; 40 extern ANPTypefaceInterfaceV0 gTypefaceI; 41 42 43 static void inval(NPP instance) { 44 browser->invalidaterect(instance, NULL); 45 } 46 47 static uint16_t rnd16(float x, int inset) { 48 int ix = (int)roundf(x) + inset; 49 if (ix < 0) { 50 ix = 0; 51 } 52 return static_cast<uint16_t>(ix); 53 } 54 55 static void inval(NPP instance, const ANPRectF& r, bool doAA) { 56 const int inset = doAA ? -1 : 0; 57 58 PluginObject *obj = reinterpret_cast<PluginObject*>(instance->pdata); 59 NPRect inval; 60 inval.left = rnd16(r.left, inset); 61 inval.top = rnd16(r.top, inset); 62 inval.right = rnd16(r.right, -inset); 63 inval.bottom = rnd16(r.bottom, -inset); 64 browser->invalidaterect(instance, &inval); 65 } 66 67 static void audioCallback(ANPAudioEvent evt, void* user, ANPAudioBuffer* buffer) { 68 switch (evt) { 69 case kMoreData_ANPAudioEvent: { 70 SoundPlay* play = reinterpret_cast<SoundPlay*>(user); 71 size_t amount = fread(buffer->bufferData, 1, buffer->size, play->file); 72 buffer->size = amount; 73 if (amount == 0) { 74 gSoundI.stop(play->track); 75 fclose(play->file); 76 play->file = NULL; 77 // TODO need to notify our main thread to delete the track now 78 } 79 80 if (play->fileSize > 0) { 81 // TODO we need to properly update the progress value 82 play->progress = 1; 83 inval(play->instance); 84 } 85 86 87 break; 88 } 89 default: 90 break; 91 } 92 } 93 94 /////////////////////////////////////////////////////////////////////////////// 95 96 AudioPlugin::AudioPlugin(NPP inst) : SubPlugin(inst) { 97 98 const char path[] = "/sdcard/sample.raw"; 99 100 // open a file stream 101 FILE* f = fopen(path, "r"); 102 gLogI.log(kDebug_ANPLogType, "--- path %s FILE %p", path, f); 103 104 // setup our private audio struct's default values 105 m_soundPlay = new SoundPlay; 106 m_soundPlay->instance = inst; 107 m_soundPlay->progress = 0; 108 m_soundPlay->fileSize = 0; 109 m_soundPlay->file = f; 110 m_soundPlay->track = NULL; 111 112 // create the audio track 113 if (f) { 114 m_soundPlay->track = gSoundI.newTrack(44100, kPCM16Bit_ANPSampleFormat, 2, audioCallback, m_soundPlay); 115 if (!m_soundPlay->track) { 116 fclose(f); 117 m_soundPlay->file = NULL; 118 } 119 } 120 121 // get the audio file's size 122 int fileDescriptor = open(path, O_RDONLY); 123 struct stat fileStatus; 124 125 if(fileDescriptor <= 0) { 126 gLogI.log(kError_ANPLogType, "fopen error"); 127 } 128 else if (fstat(fileDescriptor, &fileStatus) != 0) { 129 gLogI.log(kDebug_ANPLogType, "File Size: %d", fileStatus.st_size); 130 m_soundPlay->fileSize = fileStatus.st_size; 131 } else { 132 gLogI.log(kError_ANPLogType, "fstat error"); 133 } 134 135 // configure the UI elements 136 m_activeTouch = false; 137 138 memset(&m_trackRect, 0, sizeof(m_trackRect)); 139 memset(&m_playRect, 0, sizeof(m_playRect)); 140 memset(&m_pauseRect, 0, sizeof(m_pauseRect)); 141 memset(&m_stopRect, 0, sizeof(m_stopRect)); 142 143 m_paintTrack = gPaintI.newPaint(); 144 gPaintI.setFlags(m_paintTrack, gPaintI.getFlags(m_paintTrack) | kAntiAlias_ANPPaintFlag); 145 gPaintI.setColor(m_paintTrack, 0xFFC0C0C0); 146 147 m_paintRect = gPaintI.newPaint(); 148 gPaintI.setFlags(m_paintRect, gPaintI.getFlags(m_paintRect) | kAntiAlias_ANPPaintFlag); 149 gPaintI.setColor(m_paintRect, 0xFFA8A8A8); 150 151 m_paintText = gPaintI.newPaint(); 152 gPaintI.setFlags(m_paintText, gPaintI.getFlags(m_paintText) | kAntiAlias_ANPPaintFlag); 153 gPaintI.setColor(m_paintText, 0xFF2F4F4F); 154 gPaintI.setTextSize(m_paintText, 18); 155 156 m_paintTrackProgress = gPaintI.newPaint(); 157 gPaintI.setFlags(m_paintTrackProgress, gPaintI.getFlags(m_paintTrackProgress) | kAntiAlias_ANPPaintFlag); 158 gPaintI.setColor(m_paintTrackProgress, 0xFF545454); 159 160 m_paintActiveRect = gPaintI.newPaint(); 161 gPaintI.setFlags(m_paintActiveRect, gPaintI.getFlags(m_paintActiveRect) | kAntiAlias_ANPPaintFlag); 162 gPaintI.setColor(m_paintActiveRect, 0xFF545454); 163 164 ANPTypeface* tf = gTypefaceI.createFromName("serif", kItalic_ANPTypefaceStyle); 165 gPaintI.setTypeface(m_paintText, tf); 166 gTypefaceI.unref(tf); 167 168 //register for touch events 169 ANPEventFlags flags = kTouch_ANPEventFlag; 170 NPError err = browser->setvalue(inst, kAcceptEvents_ANPSetValue, &flags); 171 if (err != NPERR_NO_ERROR) { 172 gLogI.log(kError_ANPLogType, "Error selecting input events."); 173 } 174 } 175 176 AudioPlugin::~AudioPlugin() { 177 gPaintI.deletePaint(m_paintTrack); 178 gPaintI.deletePaint(m_paintRect); 179 gPaintI.deletePaint(m_paintText); 180 gPaintI.deletePaint(m_paintTrackProgress); 181 gPaintI.deletePaint(m_paintActiveRect); 182 if(m_soundPlay->track) 183 gSoundI.deleteTrack(m_soundPlay->track); 184 delete m_soundPlay; 185 } 186 187 bool AudioPlugin::supportsDrawingModel(ANPDrawingModel model) { 188 return (model == kBitmap_ANPDrawingModel); 189 } 190 191 void AudioPlugin::drawPlugin(const ANPBitmap& bitmap, const ANPRectI& clip) { 192 ANPCanvas* canvas = gCanvasI.newCanvas(&bitmap); 193 194 ANPRectF clipR; 195 clipR.left = clip.left; 196 clipR.top = clip.top; 197 clipR.right = clip.right; 198 clipR.bottom = clip.bottom; 199 gCanvasI.clipRect(canvas, &clipR); 200 201 draw(canvas); 202 gCanvasI.deleteCanvas(canvas); 203 } 204 205 void AudioPlugin::draw(ANPCanvas* canvas) { 206 207 PluginObject *obj = (PluginObject*) this->inst()->pdata; 208 209 gLogI.log(kError_ANPLogType, "Drawing"); 210 211 const float trackHeight = 30; 212 const float buttonWidth = 60; 213 const float buttonHeight = 30; 214 const int W = obj->window->width; 215 const int H = obj->window->height; 216 217 // color the plugin canvas 218 gCanvasI.drawColor(canvas, 0xFFCDCDCD); 219 220 // get font metrics 221 ANPFontMetrics fontMetrics; 222 gPaintI.getFontMetrics(m_paintText, &fontMetrics); 223 224 // draw the track box (1 px from the edge) 225 m_trackRect.left = 1; 226 m_trackRect.top = 1; 227 m_trackRect.right = W - 2; 228 m_trackRect.bottom = 1 + trackHeight; 229 gCanvasI.drawRect(canvas, &m_trackRect, m_paintTrack); 230 231 // draw the progress bar 232 if (m_soundPlay->progress > 0) { 233 // TODO need to draw progress bar to cover the proper percentage of the track bar 234 gCanvasI.drawRect(canvas, &m_trackRect, m_paintTrackProgress); 235 } 236 237 // draw the play box (under track box) 238 m_playRect.left = m_trackRect.left + 5; 239 m_playRect.top = m_trackRect.bottom + 10; 240 m_playRect.right = m_playRect.left + buttonWidth; 241 m_playRect.bottom = m_playRect.top + buttonHeight; 242 gCanvasI.drawRect(canvas, &m_playRect, getPaint(&m_playRect)); 243 // draw the play box (under track box) 244 const char playText[] = "Play"; 245 gCanvasI.drawText(canvas, playText, sizeof(playText)-1, m_playRect.left + 5, 246 m_playRect.top - fontMetrics.fTop, m_paintText); 247 248 // draw the pause box (under track box) 249 m_pauseRect.left = m_playRect.right + 20; 250 m_pauseRect.top = m_trackRect.bottom + 10; 251 m_pauseRect.right = m_pauseRect.left + buttonWidth; 252 m_pauseRect.bottom = m_pauseRect.top + buttonHeight; 253 gCanvasI.drawRect(canvas, &m_pauseRect, getPaint(&m_pauseRect)); 254 // draw the text in the pause box 255 const char pauseText[] = "Pause"; 256 gCanvasI.drawText(canvas, pauseText, sizeof(pauseText)-1, m_pauseRect.left + 5, 257 m_pauseRect.top - fontMetrics.fTop, m_paintText); 258 259 // draw the stop box (under track box) 260 m_stopRect.left = m_pauseRect.right + 20; 261 m_stopRect.top = m_trackRect.bottom + 10; 262 m_stopRect.right = m_stopRect.left + buttonWidth; 263 m_stopRect.bottom = m_stopRect.top + buttonHeight; 264 gCanvasI.drawRect(canvas, &m_stopRect, getPaint(&m_stopRect)); 265 // draw the text in the pause box 266 const char stopText[] = "Stop"; 267 gCanvasI.drawText(canvas, stopText, sizeof(stopText)-1, m_stopRect.left + 5, 268 m_stopRect.top - fontMetrics.fTop, m_paintText); 269 } 270 271 ANPPaint* AudioPlugin::getPaint(ANPRectF* input) { 272 return (input == m_activeRect) ? m_paintActiveRect : m_paintRect; 273 } 274 275 int16_t AudioPlugin::handleEvent(const ANPEvent* evt) { 276 NPP instance = this->inst(); 277 278 switch (evt->eventType) { 279 case kDraw_ANPEventType: 280 switch (evt->data.draw.model) { 281 case kBitmap_ANPDrawingModel: 282 drawPlugin(evt->data.draw.data.bitmap, evt->data.draw.clip); 283 return 1; 284 default: 285 break; // unknown drawing model 286 } 287 288 case kTouch_ANPEventType: { 289 int x = evt->data.touch.x; 290 int y = evt->data.touch.y; 291 if (kDown_ANPTouchAction == evt->data.touch.action) { 292 293 m_activeTouchRect = validTouch(x,y); 294 if(m_activeTouchRect) { 295 m_activeTouch = true; 296 return 1; 297 } 298 299 } else if (kUp_ANPTouchAction == evt->data.touch.action && m_activeTouch) { 300 handleTouch(x, y); 301 m_activeTouch = false; 302 return 1; 303 } else if (kCancel_ANPTouchAction == evt->data.touch.action) { 304 m_activeTouch = false; 305 } 306 break; 307 } 308 default: 309 break; 310 } 311 return 0; // unknown or unhandled event 312 } 313 314 void AudioPlugin::invalActiveRect() { 315 316 } 317 318 ANPRectF* AudioPlugin::validTouch(int x, int y) { 319 320 if (m_playRect.left && x < m_playRect.right && y > m_playRect.top && y < m_playRect.bottom) 321 return &m_playRect; 322 else if (m_pauseRect.left && x < m_pauseRect.right && y > m_pauseRect.top && y < m_pauseRect.bottom) 323 return &m_pauseRect; 324 else if (x > m_stopRect.left && x < m_stopRect.right && y > m_stopRect.top && y < m_stopRect.bottom) 325 return &m_stopRect; 326 else 327 return NULL; 328 } 329 330 void AudioPlugin::handleTouch(int x, int y) { 331 NPP instance = this->inst(); 332 333 // if the track is null then return 334 if (NULL == m_soundPlay->track) { 335 gLogI.log(kError_ANPLogType, "---- %p unable to create track", 336 instance); 337 return; 338 } 339 340 // check to make sure the currentRect matches the activeRect 341 ANPRectF* currentRect = validTouch(x,y); 342 if (m_activeTouchRect != currentRect) 343 return; 344 345 if (currentRect == &m_playRect) { 346 347 gLogI.log(kDebug_ANPLogType, "---- %p starting track (%d)", 348 m_soundPlay->track, gSoundI.isStopped(m_soundPlay->track)); 349 350 if (gSoundI.isStopped(m_soundPlay->track)) { 351 gSoundI.start(m_soundPlay->track); 352 } 353 } 354 else if (currentRect == &m_pauseRect) { 355 356 gLogI.log(kDebug_ANPLogType, "---- %p pausing track (%d)", 357 m_soundPlay->track, gSoundI.isStopped(m_soundPlay->track)); 358 359 if (!gSoundI.isStopped(m_soundPlay->track)) { 360 gSoundI.pause(m_soundPlay->track); 361 } 362 } 363 else if (currentRect == &m_stopRect) { 364 365 gLogI.log(kDebug_ANPLogType, "---- %p stopping track (%d)", 366 m_soundPlay->track, gSoundI.isStopped(m_soundPlay->track)); 367 368 if (!gSoundI.isStopped(m_soundPlay->track)) { 369 gSoundI.stop(m_soundPlay->track); 370 } 371 if (m_soundPlay->file) { 372 fseek(m_soundPlay->file, 0, SEEK_SET); 373 } 374 } 375 else { 376 return; 377 } 378 379 // set the currentRect to be the activeRect 380 m_activeRect = currentRect; 381 inval(instance); 382 } 383