1 // Sound_as.cpp: ActionScript "Sound" class, for Gnash.
2 //
3 // Copyright (C) 2009, 2010, 2011, 2012, 2013, 2014, 2016
4 // Free Software Foundation, Inc.
5 //
6 // This program is free software; you can redistribute it and/or modify
7 // it under the terms of the GNU General Public License as published by
8 // the Free Software Foundation; either version 3 of the License, or
9 // (at your option) any later version.
10 //
11 // This program is distributed in the hope that it will be useful,
12 // but WITHOUT ANY WARRANTY; without even the implied warranty of
13 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 // GNU General Public License for more details.
15 //
16 // You should have received a copy of the GNU General Public License
17 // along with this program; if not, write to the Free Software
18 // Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
19 //
20
21 #ifdef HAVE_CONFIG_H
22 #include "gnashconfig.h"
23 #endif
24
25 #include "Sound_as.h"
26
27 #include <string>
28 #include <cstdint>
29 #include <boost/optional.hpp>
30
31 #include "RunResources.h"
32 #include "log.h"
33 #include "sound_handler.h"
34 #include "MediaParser.h"
35 #include "AudioDecoder.h"
36 #include "MediaHandler.h"
37 #include "sound_definition.h"
38 #include "movie_root.h"
39 #include "movie_definition.h"
40 #include "fn_call.h"
41 #include "Global_as.h"
42 #include "GnashException.h" // for ActionException
43 #include "NativeFunction.h" // need builtin_function
44 #include "VM.h"
45 #include "namedStrings.h"
46 #include "StreamProvider.h"
47 #include "ObjectURI.h"
48 #include "Relay.h"
49 #include "Id3Info.h"
50
51 //#define GNASH_DEBUG_SOUND_AS 1
52
53 namespace gnash {
54
55 // Forward declarations
56 namespace {
57 as_value sound_new(const fn_call& fn);
58 as_value sound_attachsound(const fn_call& fn);
59 as_value sound_getbytesloaded(const fn_call& fn);
60 as_value sound_setPosition(const fn_call& fn);
61 as_value sound_areSoundsInaccessible(const fn_call& fn);
62 as_value sound_getbytestotal(const fn_call& fn);
63 as_value sound_getpan(const fn_call& fn);
64 as_value sound_setpan(const fn_call& fn);
65 as_value sound_getDuration(const fn_call& fn);
66 as_value sound_setDuration(const fn_call& fn);
67 as_value sound_gettransform(const fn_call& fn);
68 as_value sound_getPosition(const fn_call& fn);
69 as_value sound_getvolume(const fn_call& fn);
70 as_value sound_loadsound(const fn_call& fn);
71 as_value sound_settransform(const fn_call& fn);
72 as_value sound_setvolume(const fn_call& fn);
73 as_value sound_start(const fn_call& fn);
74 as_value sound_stop(const fn_call& fn);
75 as_value checkPolicyFile_getset(const fn_call& fn);
76 void attachSoundInterface(as_object& o);
77
78 /// If there is Id3 data, create an id3 member and call the onID3 function.
79 void handleId3Data(boost::optional<media::Id3Info> id3, as_object& sound);
80 }
81
82 /// A Sound object in ActionScript can control and play sound
83 //
84 /// Two types of sound are handled:
85 ///
86 /// 1. external sounds, either loaded or streamed
87 /// 2. embedded sounds, referenced by library (export) symbol.
88 //
89 /// Sound objects also control volume, pan, and other properties for a target
90 /// movieclip.
91 //
92 /// Sound_as objects
93 //
94 /// 1. May be associated with a particular DisplayObject.
95 /// 2. May be associated with one or more playing sounds.
96 //
97 /// A Sound_as that is not associated with a particular DisplayObject controls
98 /// the sound properties of the whole Movie.
99 class Sound_as : public ActiveRelay
100 {
101
102 public:
103
104 Sound_as(as_object* owner);
105
106 ~Sound_as();
107
108 /// Make this sound control the given DisplayObject
109 //
110 /// NOTE: 0 is accepted, to implement an "invalid"
111 /// controller type.
112 ///
113 void attachCharacter(DisplayObject* attachedChar);
114
115 void attachSound(int si, const std::string& name);
116
117 /// Get number of bytes loaded.
118 //
119 /// This only applies to external sounds. If unknown or not external, -1
120 /// is returned.
121 long getBytesLoaded();
122
123 /// Get total number of bytes in the external sound being loaded
124 //
125 /// This only applies to external sounds. If unknown or not external, -1
126 /// is returned.
127 long getBytesTotal();
128
129 /// Whether the Sound_as has any sound data
active() const130 bool active() const {
131 return soundId >= 0 || isStreaming;
132 }
133
134 /// Get the pan setting of the attached DisplayObject.
135 //
136 /// If no object is attached, this retrieves settings for the whole Movie.
137 void getPan();
138
139 /// Get the sound transform of the attached DisplayObject.
140 //
141 /// If no object is attached, this retrieves settings for the whole Movie.
142 void getTransform();
143
144 /// Get volume from associated resource
145 //
146 /// @return true of volume was obtained, false
147 /// otherwise (for example if the associated
148 /// DisplayObject was unloaded).
149 ///
150 bool getVolume(int& volume);
151 void setVolume(int volume);
152
153 /// Load an external sound.
154 //
155 /// The Sound object is then associated with the external sound.
156 void loadSound(const std::string& file, bool streaming);
157
158 void setPan();
159
160 void setTransform();
161
162 void start(double secsStart, int loops);
163
164 void stop(int si);
165
166 /// Get the duration of the sound.
167 //
168 /// This is only meaningful when the Sound_as object has an associated
169 /// sound, that is after attachSound or loadSound has been called.
170 size_t getDuration() const;
171
172 /// Get the position within the sound.
173 //
174 /// This is only meaningful when the Sound_as object has an associated
175 /// sound, that is after attachSound or loadSound has been called.
176 size_t getPosition() const;
177
178 std::string soundName;
179
180 private:
181
182 void markReachableResources() const;
183
184 std::unique_ptr<CharacterProxy> _attachedCharacter;
185 int soundId;
186 bool externalSound;
187 bool isStreaming;
188
189 sound::sound_handler* _soundHandler;
190
191 media::MediaHandler* _mediaHandler;
192
193 std::unique_ptr<media::MediaParser> _mediaParser;
194
195 std::unique_ptr<media::AudioDecoder> _audioDecoder;
196
197 /// Number of milliseconds into the sound to start it
198 //
199 /// This is set by start()
200 std::uint64_t _startTime;
201
202 std::unique_ptr<std::uint8_t[]> _leftOverData;
203 std::uint8_t* _leftOverPtr;
204 std::uint32_t _leftOverSize;
205
206 /// This is a sound_handler::aux_streamer_ptr type.
207 static unsigned int getAudioWrapper(void *owner, std::int16_t* samples,
208 unsigned int nSamples, bool& etEOF);
209
210 unsigned int getAudio(std::int16_t* samples, unsigned int nSamples,
211 bool& atEOF);
212
213 /// The aux streamer for sound handler
214 sound::InputStream* _inputStream;
215
216 int remainingLoops;
217
218 /// Query media parser for audio info, create decoder and attach aux streamer
219 /// if found.
220 ///
221 /// @return an InputStream* if audio found and aux streamer attached,
222 /// 0 if no audio found.
223 ///
224 /// May throw a MediaException if audio was found but
225 /// audio decoder could not be created
226 ///
227 sound::InputStream* attachAuxStreamerIfNeeded();
228
229 /// Register a timer for audio info probing
230 void startProbeTimer();
231
232 /// Unregister the probe timer
233 void stopProbeTimer();
234
235 virtual void update();
236
237 /// Probe audio
238 void probeAudio();
239
240 std::atomic<bool> _soundCompleted;
241
242 /// Setter for _soundCompleted
243 void markSoundCompleted(bool completed);
244
245 bool _soundLoaded;
246
247 // Does this sound have a live input stream?
isAttached() const248 bool isAttached() const {
249 return (_inputStream);
250 }
251
252 };
253
Sound_as(as_object * owner)254 Sound_as::Sound_as(as_object* owner)
255 :
256 ActiveRelay(owner),
257 _attachedCharacter(),
258 soundId(-1),
259 externalSound(false),
260 isStreaming(false),
261 _soundHandler(getRunResources(*owner).soundHandler()),
262 _mediaHandler(getRunResources(*owner).mediaHandler()),
263 _startTime(0),
264 _leftOverData(),
265 _leftOverPtr(nullptr),
266 _leftOverSize(0),
267 _inputStream(nullptr),
268 remainingLoops(0),
269 _soundCompleted(false),
270 _soundLoaded(false)
271 {
272 }
273
~Sound_as()274 Sound_as::~Sound_as()
275 {
276 // Just in case...
277 if (_inputStream && _soundHandler) {
278 _soundHandler->unplugInputStream(_inputStream);
279 _inputStream=nullptr;
280 }
281
282 }
283
284 // extern (used by Global.cpp)
285 void
sound_class_init(as_object & where,const ObjectURI & uri)286 sound_class_init(as_object& where, const ObjectURI& uri)
287 {
288
289 Global_as& gl = getGlobal(where);
290 as_object* proto = createObject(gl);
291 as_object* cl = gl.createClass(&sound_new, proto);
292 attachSoundInterface(*proto);
293 proto->set_member_flags(NSV::PROP_CONSTRUCTOR, PropFlags::readOnly);
294 proto->set_member_flags(NSV::PROP_uuPROTOuu, PropFlags::readOnly, 0);
295
296 where.init_member(uri, cl, as_object::DefaultFlags);
297 }
298
299 void
registerSoundNative(as_object & global)300 registerSoundNative(as_object& global)
301 {
302 VM& vm = getVM(global);
303 vm.registerNative(sound_getpan, 500, 0);
304 vm.registerNative(sound_gettransform, 500, 1);
305 vm.registerNative(sound_getvolume, 500, 2);
306 vm.registerNative(sound_setpan, 500, 3);
307 vm.registerNative(sound_settransform, 500, 4);
308 vm.registerNative(sound_setvolume, 500, 5);
309 vm.registerNative(sound_stop, 500, 6);
310 vm.registerNative(sound_attachsound, 500, 7);
311 vm.registerNative(sound_start, 500, 8);
312 vm.registerNative(sound_getDuration, 500, 9);
313 vm.registerNative(sound_setDuration, 500, 10);
314 vm.registerNative(sound_getPosition, 500, 11);
315 vm.registerNative(sound_setPosition, 500, 12);
316 vm.registerNative(sound_loadsound, 500, 13);
317 vm.registerNative(sound_getbytesloaded, 500, 14);
318 vm.registerNative(sound_getbytestotal, 500, 15);
319 vm.registerNative(sound_areSoundsInaccessible, 500, 16);
320 }
321
322 /*private*/
323 void
startProbeTimer()324 Sound_as::startProbeTimer()
325 {
326 getRoot(owner()).addAdvanceCallback(this);
327 }
328
329 /*private*/
330 void
stopProbeTimer()331 Sound_as::stopProbeTimer()
332 {
333 #ifdef GNASH_DEBUG_SOUND_AS
334 log_debug("stopProbeTimer called");
335 #endif
336 getRoot(owner()).removeAdvanceCallback(this);
337 }
338
339 void
update()340 Sound_as::update()
341 {
342 probeAudio();
343
344 if (active()) {
345 owner().set_member(NSV::PROP_DURATION, getDuration());
346 owner().set_member(NSV::PROP_POSITION, getPosition());
347 }
348 }
349
350 void
probeAudio()351 Sound_as::probeAudio()
352 {
353 #ifdef USE_SOUND
354 if ( ! externalSound ) {
355 // Only probe for sound complete
356 assert(_soundHandler);
357 assert(!_soundCompleted.load());
358 if (!_soundHandler->isSoundPlaying(soundId)) {
359 stopProbeTimer();
360 // dispatch onSoundComplete
361 callMethod(&owner(), NSV::PROP_ON_SOUND_COMPLETE);
362 }
363 return;
364 }
365
366 if (!_mediaParser) return; // nothing to do here w/out a media parser
367
368 #ifdef USE_MEDIA
369 if ( ! _soundLoaded ) {
370 #ifdef GNASH_DEBUG_SOUND_AS
371 log_debug("Probing audio for load");
372 #endif
373 if (_mediaParser->parsingCompleted()) {
374
375 _soundLoaded = true;
376
377 if (!isStreaming) {
378 stopProbeTimer(); // will be re-started on Sound.start()
379 }
380 bool success = _mediaParser->getAudioInfo() != nullptr;
381 callMethod(&owner(), NSV::PROP_ON_LOAD, success);
382
383 // TODO: check if this should be called anyway.
384 if (success) handleId3Data(_mediaParser->getId3Info(), owner());
385 }
386
387 // If this is an event sound, we are done. Otherwise, if there is
388 // any data available, we should start playback.
389 if (!isStreaming || _mediaParser->isBufferEmpty()) {
390 return;
391 }
392 }
393
394 if (isAttached()) {
395 #ifdef GNASH_DEBUG_SOUND_AS
396 log_debug("Probing audio for end");
397 #endif
398
399 if (_soundCompleted.load()) {
400 // when _soundCompleted is true we're NOT attached !
401 // MediaParser may be still needed,
402 // if this is a non-streaming sound
403 if ( isStreaming ) {
404 _mediaParser.reset(); // no use for this anymore...
405 }
406 _inputStream = nullptr;
407 _soundCompleted = false;
408 stopProbeTimer();
409
410 // dispatch onSoundComplete
411 callMethod(&owner(), NSV::PROP_ON_SOUND_COMPLETE);
412 }
413 }
414 else {
415 #ifdef GNASH_DEBUG_SOUND_AS
416 log_debug("Probing audio for start");
417 #endif
418
419 bool parsingCompleted = _mediaParser->parsingCompleted();
420 try {
421 log_debug("Attaching aux streamer");
422 _inputStream = attachAuxStreamerIfNeeded();
423 }
424 catch (const MediaException& e) {
425 assert(!_inputStream);
426 assert(!_audioDecoder.get());
427 log_error(_("Could not create audio decoder: %s"), e.what());
428 _mediaParser.reset(); // no use for this anymore...
429 stopProbeTimer();
430 return;
431 }
432
433 if ( ! _inputStream ) {
434 if ( parsingCompleted ) {
435 log_error(_("No audio in Sound input."));
436 stopProbeTimer();
437 _mediaParser.reset(); // no use for this anymore...
438 } else {
439 // keep probing
440 }
441 } else {
442 // An audio decoder was constructed, good!
443 assert(_audioDecoder.get());
444 }
445 }
446 #endif // USE_MEDIA
447 #endif // USE_SOUND
448 }
449
450 void
markReachableResources() const451 Sound_as::markReachableResources() const
452 {
453 if (_attachedCharacter) {
454 _attachedCharacter->setReachable();
455 }
456 }
457
458 void
markSoundCompleted(bool completed)459 Sound_as::markSoundCompleted(bool completed)
460 {
461 _soundCompleted=completed;
462 }
463
464 void
attachCharacter(DisplayObject * attachTo)465 Sound_as::attachCharacter(DisplayObject* attachTo)
466 {
467 _attachedCharacter.reset(new CharacterProxy(attachTo, getRoot(owner())));
468 }
469
470 void
attachSound(int si,const std::string & name)471 Sound_as::attachSound(int si, const std::string& name)
472 {
473 soundId = si;
474 soundName = name;
475
476 owner().set_member(NSV::PROP_DURATION, getDuration());
477 owner().set_member(NSV::PROP_POSITION, getPosition());
478
479 }
480
481 long
getBytesLoaded()482 Sound_as::getBytesLoaded()
483 {
484 if ( _mediaParser ) {
485 return _mediaParser->getBytesLoaded();
486 }
487
488 return -1;
489 }
490
491 long
getBytesTotal()492 Sound_as::getBytesTotal()
493 {
494 if ( _mediaParser ) {
495 return _mediaParser->getBytesTotal();
496 }
497
498 return -1;
499 }
500
501 void
getPan()502 Sound_as::getPan()
503 {
504 LOG_ONCE(log_unimpl(__FUNCTION__));
505 }
506
507 void
getTransform()508 Sound_as::getTransform()
509 {
510 LOG_ONCE(log_unimpl(__FUNCTION__));
511 }
512
513 bool
getVolume(int & volume)514 Sound_as::getVolume(int& volume)
515 {
516 // TODO: check what takes precedence in case we
517 // have both an attached DisplayObject *and*
518 // some other sound...
519 //
520 if ( _attachedCharacter ) {
521 //log_debug("Sound has an attached DisplayObject");
522 DisplayObject* ch = _attachedCharacter->get();
523 if (! ch) {
524 log_debug("Character attached to Sound was unloaded and "
525 "couldn't rebind");
526 return false;
527 }
528 volume = ch->getVolume();
529 return true;
530 }
531
532 // If we're not attached to a DisplayObject we'll need to query
533 // sound_handler for volume. If we have no sound handler, we
534 // can't do much, so we'll return false
535 if (!_soundHandler) {
536 log_debug("We have no sound handler here...");
537 return false;
538 }
539
540 // Now, we may be controlling a specific sound or
541 // the final output as a whole.
542 // If soundId is -1 we're controlling as a whole
543 //
544 if (soundId == -1) {
545 volume = _soundHandler->getFinalVolume();
546 } else {
547 volume = _soundHandler->get_volume(soundId);
548 }
549
550 return true;
551 }
552
553 void
loadSound(const std::string & file,bool streaming)554 Sound_as::loadSound(const std::string& file, bool streaming)
555 {
556 if (!_mediaHandler || !_soundHandler) {
557 log_debug("No media or sound handlers, won't load any sound");
558 return;
559 }
560
561 /// If we are already streaming stop doing so as we'll replace
562 /// the media parser
563 if (_inputStream) {
564 _soundHandler->unplugInputStream(_inputStream);
565 _inputStream = nullptr;
566 }
567
568 /// Mark sound as not being loaded
569 // TODO: should we check for _soundLoaded == true?
570 _soundLoaded = false;
571
572 /// Delete any media parser being used (make sure we have detached!)
573 _mediaParser.reset();
574
575 /// Start at offset 0, in case a previous ::start() call
576 /// changed that.
577 _startTime = 0;
578
579 const RunResources& rr = getRunResources(owner());
580 URL url(file, rr.streamProvider().baseURL());
581
582 const RcInitFile& rcfile = RcInitFile::getDefaultInstance();
583
584 const StreamProvider& streamProvider = rr.streamProvider();
585 std::unique_ptr<IOChannel> inputStream(streamProvider.getStream(url,
586 rcfile.saveStreamingMedia()));
587
588 if (!inputStream.get()) {
589 log_error(_("Gnash could not open this URL: %s"), url );
590 // dispatch onLoad (false)
591 callMethod(&owner(), NSV::PROP_ON_LOAD, false);
592 return;
593 }
594
595 externalSound = true;
596 isStreaming = streaming;
597
598 _mediaParser = std::move(_mediaHandler->createMediaParser(std::move(inputStream)));
599 if (!_mediaParser) {
600 log_error(_("Unable to create parser for Sound at %s"), url);
601 // not necessarely correct, the stream might have been found...
602 // dispatch onLoad (false)
603 callMethod(&owner(), NSV::PROP_ON_LOAD, false);
604 return;
605 }
606
607 // TODO: use global _soundbuftime
608 if (isStreaming) {
609 _mediaParser->setBufferTime(60000); // one minute buffer... should be fine
610 } else {
611 // If this is an event sound, we must not limit buffering (parsing),
612 // because onLoad will not be called until we have finished doing so.
613 _mediaParser->setBufferTime(std::numeric_limits<std::uint64_t>::max());
614 }
615
616 startProbeTimer();
617
618 owner().set_member(NSV::PROP_DURATION, getDuration());
619 owner().set_member(NSV::PROP_POSITION, getPosition());
620 }
621
622 sound::InputStream*
attachAuxStreamerIfNeeded()623 Sound_as::attachAuxStreamerIfNeeded()
624 {
625 media::AudioInfo* audioInfo = _mediaParser->getAudioInfo();
626 if (!audioInfo) return nullptr;
627
628 // the following may throw an exception
629 _audioDecoder.reset(_mediaHandler->createAudioDecoder(*audioInfo).release());
630
631 // start playing ASAP, a call to ::start will just change _startTime
632 #ifdef GNASH_DEBUG_SOUND_AS
633 log_debug("Attaching the aux streamer");
634 #endif
635 return _soundHandler->attach_aux_streamer(getAudioWrapper, (void*) this);
636 }
637
638 void
setPan()639 Sound_as::setPan()
640 {
641 LOG_ONCE(log_unimpl(__FUNCTION__));
642 }
643
644 void
setTransform()645 Sound_as::setTransform()
646 {
647 LOG_ONCE(log_unimpl(__FUNCTION__));
648 }
649
650 void
setVolume(int volume)651 Sound_as::setVolume(int volume)
652 {
653 // TODO: check what takes precedence in case we
654 // have both an attached DisplayObject *and*
655 // some other sound...
656 //
657 if ( _attachedCharacter ) {
658 DisplayObject* ch = _attachedCharacter->get();
659 if ( ! ch ) {
660 log_debug("Character attached to Sound was unloaded and "
661 "couldn't rebind");
662 return;
663 }
664 ch->setVolume(volume);
665 return;
666 }
667
668 // If we're not attached to a DisplayObject we'll need to use
669 // sound_handler for volume. If we have no sound handler, we
670 // can't do much, so we'll just return
671 if (!_soundHandler) {
672 return;
673 }
674
675 // Now, we may be controlling a specific sound or
676 // the final output as a whole.
677 // If soundId is -1 we're controlling as a whole
678 //
679 if ( soundId == -1 ) {
680 _soundHandler->setFinalVolume(volume);
681 } else {
682 _soundHandler->set_volume(soundId, volume);
683 }
684 }
685
686 void
start(double secOff,int loops)687 Sound_as::start(double secOff, int loops)
688 {
689 if ( ! _soundHandler ) {
690 log_error(_("No sound handler, nothing to start..."));
691 return;
692 }
693
694 #ifdef USE_SOUND
695 if (externalSound) {
696 if ( ! _mediaParser ) {
697 log_error(_("No MediaParser initialized, can't start an external sound"));
698 return;
699 }
700
701 if (isStreaming) {
702 IF_VERBOSE_ASCODING_ERRORS(
703 log_aserror(_("Sound.start() has no effect on a streaming Sound"));
704 );
705 return;
706 }
707
708 // Always seek as we might be called during or after some playing...
709 {
710 _startTime = secOff * 1000;
711 std::uint32_t seekms = std::uint32_t(secOff * 1000);
712 // TODO: std::lock_guard<std::mutex> parserLock(_parserMutex);
713 bool seeked = _mediaParser->seek(seekms); // well, we try...
714 log_debug("Seeked MediaParser to %d, returned: %d", seekms, seeked);
715 }
716
717
718 // Save how many loops to do (not when streaming)
719 if (loops > 0) {
720 remainingLoops = loops;
721 }
722
723 startProbeTimer();
724
725 } else {
726 unsigned int inPoint = 0;
727
728 if ( secOff > 0 ) {
729 inPoint = (secOff*44100);
730 }
731
732 log_debug("Sound.start: secOff:%d loops:%d", secOff, loops);
733
734 _soundHandler->startSound(
735 soundId,
736 loops,
737 nullptr, // envelopes
738 true, // allow multiple instances (checked)
739 inPoint
740 );
741
742 startProbeTimer(); // to dispatch onSoundComplete
743 }
744 #endif // USE_SOUND
745 }
746
747 void
stop(int si)748 Sound_as::stop(int si)
749 {
750 if ( ! _soundHandler ) {
751 log_error(_("No sound handler, nothing to stop..."));
752 return;
753 }
754
755 #ifdef USE_SOUND
756 // stop the sound
757 if (si < 0) {
758 if (externalSound) {
759 if ( _inputStream ) {
760 _soundHandler->unplugInputStream(_inputStream);
761 _inputStream=nullptr;
762 }
763 } else {
764 if ( ! _attachedCharacter ) {
765 // See https://savannah.gnu.org/bugs/index.php?33888
766 _soundHandler->stopAllEventSounds();
767 } else {
768 _soundHandler->stopEventSound(soundId);
769 }
770 }
771 } else {
772 _soundHandler->stopEventSound(si);
773 }
774 #endif // USE_SOUND
775 }
776
777 size_t
getDuration() const778 Sound_as::getDuration() const
779 {
780 if ( ! _soundHandler ) {
781 log_error(_("No sound handler, can't check duration..."));
782 return 0;
783 }
784
785 // If this is a event sound get the info from the soundhandler
786 if (!externalSound) {
787 return _soundHandler->get_duration(soundId);
788 }
789
790 // If we have a media parser (we'd do for an externalSound)
791 // try fetching duration from it
792 if ( _mediaParser ) {
793 media::AudioInfo* info = _mediaParser->getAudioInfo();
794 if ( info ) {
795 return info->duration;
796 }
797 }
798
799 return 0;
800 }
801
802 size_t
getPosition() const803 Sound_as::getPosition() const
804 {
805 if (!_soundHandler) {
806 log_error(_("No sound handler, can't check position (we're "
807 "likely not playing anyway)..."));
808 return 0;
809 }
810 #ifdef USE_SOUND
811 // If this is a event sound get the info from the soundhandler
812 if (!externalSound) {
813 return _soundHandler->tell(soundId);
814 }
815
816 #ifdef USE_MEDIA
817 if (_mediaParser) {
818 std::uint64_t ts;
819 if ( _mediaParser->nextAudioFrameTimestamp(ts) ) {
820 return ts;
821 }
822 }
823 #endif // USE_MEDIA
824 #endif // USE_SOUND
825
826 return 0;
827 }
828
829
830 unsigned int
getAudio(std::int16_t * samples,unsigned int nSamples,bool & atEOF)831 Sound_as::getAudio(std::int16_t* samples, unsigned int nSamples, bool& atEOF)
832 {
833 #if defined USE_SOUND && defined USE_MEDIA
834 std::uint8_t* stream = reinterpret_cast<std::uint8_t*>(samples);
835 int len = nSamples*2;
836 //GNASH_REPORT_FUNCTION;
837
838 while (len) {
839 if ( ! _leftOverData ) {
840 bool parsingComplete = _mediaParser->parsingCompleted(); // check *before* calling nextAudioFrame
841 std::unique_ptr<media::EncodedAudioFrame> frame = _mediaParser->nextAudioFrame();
842 if ( ! frame.get() ) {
843 // just wait some more if parsing isn't complete yet
844 if ( ! parsingComplete ) {
845 //log_debug("Parsing not complete and no more audio frames in input, try again later");
846 break;
847 }
848
849 // or detach and stop here...
850 // (should really honour loopings if any,
851 // but that should be only done for non-streaming sound!)
852 //log_debug("Parsing complete and no more audio frames in input, detaching");
853
854 markSoundCompleted(true);
855
856 // Setting atEOF to true will detach us.
857 // We should change _inputStream, but need thread safety!
858 // So on probeAudio, if _soundCompleted is set
859 // we'll consider ourselves detached already and set
860 // _inputStream to zero
861 atEOF=true;
862 return nSamples-(len/2);
863 }
864
865 // if we've been asked to start at a specific time, skip
866 // any frame with earlier timestamp
867 if ( frame->timestamp < _startTime ) {
868 //log_debug("This audio frame timestamp (%d) < requested start time (%d)", frame->timestamp, _startTime);
869 continue;
870 }
871
872 _leftOverData.reset( _audioDecoder->decode(*frame, _leftOverSize) );
873 _leftOverPtr = _leftOverData.get();
874 if ( ! _leftOverData ) {
875 log_error(_("No samples decoded from input of %d bytes"),
876 frame->dataSize);
877 continue;
878 }
879
880 //log_debug(" decoded %d bytes of audio", _leftOverSize);
881 }
882
883 assert( !(_leftOverSize%2) );
884
885 int n = std::min<int>(_leftOverSize, len);
886 //log_debug(" consuming %d bytes of decoded audio", n);
887
888 std::copy(_leftOverPtr, _leftOverPtr+n, stream);
889
890 stream += n;
891 _leftOverPtr += n;
892 _leftOverSize -= n;
893 len -= n;
894
895 if (_leftOverSize == 0) {
896 _leftOverData.reset();
897 _leftOverPtr = nullptr;
898 }
899
900 }
901
902 // drop any queued video frame
903 while (_mediaParser->nextVideoFrame().get()) {};
904
905 atEOF=false;
906 return nSamples-(len/2);
907 #else // USE_MEDIA && USE_SOUND
908 UNUSED(samples);
909 UNUSED(nSamples);
910 UNUSED(atEOF);
911 return 0;
912 #endif
913 }
914
915 // audio callback is running in sound handler thread
916 unsigned int
getAudioWrapper(void * owner,std::int16_t * samples,unsigned int nSamples,bool & atEOF)917 Sound_as::getAudioWrapper(void* owner, std::int16_t* samples,
918 unsigned int nSamples, bool& atEOF)
919 {
920 Sound_as* so = static_cast<Sound_as*>(owner);
921 return so->getAudio(samples, nSamples, atEOF);
922 }
923
924
925 namespace {
926
927 void
attachSoundInterface(as_object & o)928 attachSoundInterface(as_object& o)
929 {
930
931 int flags = PropFlags::dontEnum |
932 PropFlags::dontDelete |
933 PropFlags::readOnly;
934
935 VM& vm = getVM(o);
936 o.init_member("getPan", vm.getNative(500, 0), flags);
937 o.init_member("getTransform", vm.getNative(500, 1), flags);
938 o.init_member("getVolume", vm.getNative(500, 2), flags);
939 o.init_member("setPan", vm.getNative(500, 3), flags);
940 o.init_member("setTransform", vm.getNative(500, 4), flags);
941 o.init_member("setVolume", vm.getNative(500, 5), flags);
942 o.init_member("stop", vm.getNative(500, 6), flags);
943 o.init_member("attachSound", vm.getNative(500, 7), flags);
944 o.init_member("start", vm.getNative(500, 8), flags);
945
946 int flagsn6 = flags | PropFlags::onlySWF6Up;
947
948 o.init_member("getDuration", vm.getNative(500, 9), flagsn6);
949 o.init_member("setDuration", vm.getNative(500, 10), flagsn6);
950 o.init_member("getPosition", vm.getNative(500, 11), flagsn6);
951 o.init_member("setPosition", vm.getNative(500, 12), flagsn6);
952 o.init_member("loadSound", vm.getNative(500, 13), flagsn6);
953 o.init_member("getBytesLoaded", vm.getNative(500, 14), flagsn6);
954 o.init_member("getBytesTotal", vm.getNative(500, 15), flagsn6);
955
956 int flagsn9 = PropFlags::dontEnum |
957 PropFlags::dontDelete |
958 PropFlags::readOnly |
959 PropFlags::onlySWF9Up;
960
961 o.init_member("areSoundsInaccessible", vm.getNative(500, 16), flagsn9);
962
963 int fl_hp = PropFlags::dontEnum | PropFlags::dontDelete;
964
965 o.init_property("checkPolicyFile", &checkPolicyFile_getset,
966 &checkPolicyFile_getset, fl_hp);
967 }
968
969
970 as_value
sound_new(const fn_call & fn)971 sound_new(const fn_call& fn)
972 {
973 as_object* so = ensure<ValidThis>(fn);
974 Sound_as* s(new Sound_as(so));
975 so->setRelay(s);
976
977 if (fn.nargs) {
978 IF_VERBOSE_ASCODING_ERRORS(
979 if (fn.nargs > 1) {
980 std::stringstream ss; fn.dump_args(ss);
981 log_aserror(_("new Sound(%d) : args after first one ignored"),
982 ss.str());
983 }
984 );
985
986 const as_value& arg0 = fn.arg(0);
987
988 if (!arg0.is_null() && !arg0.is_undefined()) {
989
990 as_object* obj = toObject(arg0, getVM(fn));
991 DisplayObject* ch = get<DisplayObject>(obj);
992 IF_VERBOSE_ASCODING_ERRORS(
993 if (!ch) {
994 std::stringstream ss; fn.dump_args(ss);
995 log_aserror(_("new Sound(%s) : first argument isn't null "
996 "or undefined, and isn't a DisplayObject. "
997 "We'll take as an invalid DisplayObject ref."),
998 ss.str());
999 }
1000 );
1001
1002 s->attachCharacter(ch);
1003 }
1004 }
1005
1006 return as_value();
1007 }
1008
1009 as_value
sound_start(const fn_call & fn)1010 sound_start(const fn_call& fn)
1011 {
1012 IF_VERBOSE_ACTION (
1013 log_action(_("-- start sound"));
1014 )
1015 Sound_as* so = ensure<ThisIsNative<Sound_as> >(fn);
1016 int loop = 0;
1017 double secondOffset = 0;
1018
1019 if (fn.nargs > 0) {
1020 secondOffset = toNumber(fn.arg(0), getVM(fn));
1021
1022 if (fn.nargs > 1) {
1023 // Negative values count as playing once (aka looping 0 times)
1024 loop = std::max(0, toInt(fn.arg(1), getVM(fn)) - 1);
1025 }
1026 }
1027 so->start(secondOffset, loop);
1028 return as_value();
1029 }
1030
1031 as_value
sound_stop(const fn_call & fn)1032 sound_stop(const fn_call& fn)
1033 {
1034 IF_VERBOSE_ACTION (
1035 log_action(_("-- stop sound "));
1036 )
1037 Sound_as* so = ensure<ThisIsNative<Sound_as> >(fn);
1038
1039 int si = -1;
1040
1041 if (fn.nargs > 0) {
1042 const std::string& name = fn.arg(0).to_string();
1043
1044 // check the import.
1045 const movie_definition* def = fn.callerDef;
1046 assert(def);
1047
1048 const std::uint16_t id = def->exportID(name);
1049 if (!id) {
1050 IF_VERBOSE_MALFORMED_SWF(
1051 log_swferror(_("No such export '%s'"),
1052 name);
1053 );
1054 return as_value();
1055 }
1056
1057 sound_sample* ss = def->get_sound_sample(id);
1058 if (!ss) {
1059 IF_VERBOSE_MALFORMED_SWF(
1060 log_swferror(_("Export '%s' is not a sound"), name);
1061 );
1062 return as_value();
1063 }
1064
1065 si = ss->m_sound_handler_id;
1066 }
1067
1068 so->stop(si);
1069 return as_value();
1070 }
1071
1072 as_value
sound_attachsound(const fn_call & fn)1073 sound_attachsound(const fn_call& fn)
1074 {
1075 IF_VERBOSE_ACTION(
1076 log_action(_("-- attach sound"));
1077 )
1078
1079 if (fn.nargs < 1) {
1080 IF_VERBOSE_ASCODING_ERRORS(
1081 log_aserror(_("attach sound needs one argument"));
1082 );
1083 return as_value();
1084 }
1085
1086 Sound_as* so = ensure<ThisIsNative<Sound_as> >(fn);
1087
1088 const std::string& name = fn.arg(0).to_string();
1089 if (name.empty()) {
1090 IF_VERBOSE_ASCODING_ERRORS(
1091 log_aserror(_("attachSound needs a non-empty string"));
1092 );
1093 return as_value();
1094 }
1095
1096 // check the import.
1097 // NOTE: we should be checking in the SWF containing the calling code
1098 // (see 'winter bell' from orisinal morning sunshine for a testcase)
1099 const movie_definition* def = fn.callerDef;
1100 assert(def);
1101
1102
1103 const std::uint16_t id = def->exportID(name);
1104 if (!id) {
1105 IF_VERBOSE_MALFORMED_SWF(
1106 log_swferror(_("No such export '%s'"),
1107 name);
1108 );
1109 return as_value();
1110 }
1111
1112 sound_sample* ss = def->get_sound_sample(id);
1113 if (!ss) {
1114 IF_VERBOSE_MALFORMED_SWF(
1115 log_swferror(_("Export '%s'is not a sound"), name);
1116 );
1117 return as_value();
1118 }
1119
1120 const int si = ss->m_sound_handler_id;
1121
1122 // sanity check
1123 assert(si >= 0);
1124 so->attachSound(si, name);
1125
1126 return as_value();
1127 }
1128
1129 as_value
sound_getbytesloaded(const fn_call & fn)1130 sound_getbytesloaded(const fn_call& fn)
1131 {
1132 Sound_as* so = ensure<ThisIsNative<Sound_as> >(fn);
1133 long loaded = so->getBytesLoaded();
1134 if (loaded < 0) return as_value();
1135 return as_value(loaded);
1136 }
1137
1138 as_value
sound_getbytestotal(const fn_call & fn)1139 sound_getbytestotal(const fn_call& fn)
1140 {
1141 Sound_as* so = ensure<ThisIsNative<Sound_as> >(fn);
1142 long total = so->getBytesTotal();
1143 if (total < 0) return as_value();
1144 return as_value(total);
1145 }
1146
1147 as_value
sound_getpan(const fn_call &)1148 sound_getpan(const fn_call& /*fn*/)
1149 {
1150 LOG_ONCE(log_unimpl(_("Sound.getPan()")));
1151 return as_value();
1152 }
1153
1154 as_value
sound_getDuration(const fn_call & fn)1155 sound_getDuration(const fn_call& fn)
1156 {
1157 Sound_as* so = ensure<ThisIsNative<Sound_as> >(fn);
1158 if (!so->active()) return as_value();
1159 return as_value(so->getDuration());
1160 }
1161
1162 as_value
sound_setDuration(const fn_call &)1163 sound_setDuration(const fn_call& /*fn*/)
1164 {
1165 LOG_ONCE(log_unimpl(_("Sound.setDuration()")));
1166 return as_value();
1167 }
1168
1169 as_value
sound_getPosition(const fn_call & fn)1170 sound_getPosition(const fn_call& fn)
1171 {
1172 Sound_as* so = ensure<ThisIsNative<Sound_as> >(fn);
1173 if (!so->active()) return as_value();
1174 return as_value(so->getPosition());
1175 }
1176
1177 as_value
sound_setPosition(const fn_call &)1178 sound_setPosition(const fn_call& /*fn*/)
1179 {
1180 LOG_ONCE(log_unimpl(_("Sound.setPosition()")));
1181 return as_value();
1182 }
1183
1184 as_value
sound_gettransform(const fn_call &)1185 sound_gettransform(const fn_call& /*fn*/)
1186 {
1187 LOG_ONCE( log_unimpl(_("Sound.getTransform()")));
1188 return as_value();
1189 }
1190
1191 as_value
sound_getvolume(const fn_call & fn)1192 sound_getvolume(const fn_call& fn)
1193 {
1194
1195 Sound_as* so = ensure<ThisIsNative<Sound_as> >(fn);
1196
1197 if ( fn.nargs ) {
1198 IF_VERBOSE_ASCODING_ERRORS(
1199 std::stringstream ss; fn.dump_args(ss);
1200 log_aserror(_("Sound.getVolume(%s) : arguments ignored"));
1201 );
1202 }
1203
1204 int volume;
1205 if (so->getVolume(volume)) return as_value(volume);
1206 return as_value();
1207 }
1208
1209 as_value
sound_loadsound(const fn_call & fn)1210 sound_loadsound(const fn_call& fn)
1211 {
1212 Sound_as* so = ensure<ThisIsNative<Sound_as> >(fn);
1213
1214 if (!fn.nargs) {
1215 IF_VERBOSE_ASCODING_ERRORS(
1216 log_aserror(_("Sound.loadSound() needs at least 1 argument"));
1217 );
1218 return as_value();
1219 }
1220
1221 std::string url = fn.arg(0).to_string();
1222
1223 bool streaming = false;
1224 if ( fn.nargs > 1 ) {
1225 streaming = toBool(fn.arg(1), getVM(fn));
1226
1227 IF_VERBOSE_ASCODING_ERRORS(
1228 if ( fn.nargs > 2 )
1229 {
1230 std::stringstream ss; fn.dump_args(ss);
1231 log_aserror(_("Sound.loadSound(%s): arguments after first 2 "
1232 "discarded"), ss.str());
1233 }
1234 );
1235 }
1236
1237 so->loadSound(url, streaming);
1238
1239 return as_value();
1240 }
1241
1242 as_value
sound_setpan(const fn_call &)1243 sound_setpan(const fn_call& /*fn*/)
1244 {
1245 LOG_ONCE(log_unimpl(_("Sound.setPan()")));
1246 return as_value();
1247 }
1248
1249 as_value
sound_settransform(const fn_call &)1250 sound_settransform(const fn_call& /*fn*/)
1251 {
1252 LOG_ONCE(log_unimpl(_("Sound.setTransform()")));
1253 return as_value();
1254 }
1255
1256 as_value
sound_setvolume(const fn_call & fn)1257 sound_setvolume(const fn_call& fn)
1258 {
1259 if (fn.nargs < 1) {
1260 IF_VERBOSE_ASCODING_ERRORS(
1261 log_aserror(_("set volume of sound needs one argument"));
1262 );
1263 return as_value();
1264 }
1265
1266 Sound_as* so = ensure<ThisIsNative<Sound_as> >(fn);
1267 int volume = (int) toNumber(fn.arg(0), getVM(fn));
1268
1269 so->setVolume(volume);
1270 return as_value();
1271 }
1272
1273 as_value
checkPolicyFile_getset(const fn_call &)1274 checkPolicyFile_getset(const fn_call& /*fn*/)
1275 {
1276 LOG_ONCE(log_unimpl(_("Sound.checkPolicyFile")));
1277 return as_value();
1278 }
1279
1280 as_value
sound_areSoundsInaccessible(const fn_call &)1281 sound_areSoundsInaccessible(const fn_call& /*fn*/)
1282 {
1283 // TODO: I guess this would have to do with permissions (crossdomain stuff)
1284 // more then capability.
1285 // See http://www.actionscript.org/forums/showthread.php3?t=160028
1286 //
1287 // naive test shows this always being undefined..
1288 //
1289 LOG_ONCE(log_unimpl(_("Sound.areSoundsInaccessible()")));
1290 return as_value();
1291 }
1292
1293 void
handleId3Data(boost::optional<media::Id3Info> id3,as_object & sound)1294 handleId3Data(boost::optional<media::Id3Info> id3, as_object& sound)
1295 {
1296 if (!id3) return;
1297 VM& vm = getVM(sound);
1298
1299 as_object* o = new as_object(getGlobal(sound));
1300
1301 // TODO: others.
1302 if (id3->album) o->set_member(getURI(vm, "album"), *id3->album);
1303 if (id3->year) o->set_member(getURI(vm, "year"), *id3->year);
1304
1305 // Add Sound.id3 member
1306 const ObjectURI& id3prop = getURI(vm, "id3");
1307 sound.set_member(id3prop, o);
1308
1309 // Notify onID3 function.
1310 const ObjectURI& onID3 = getURI(vm, "onID3");
1311 callMethod(&sound, onID3);
1312 }
1313
1314 } // anonymous namespace
1315 } // gnash namespace
1316
1317 // local Variables:
1318 // mode: C++
1319 // indent-tabs-mode: nil
1320 // End:
1321
1322