diff --git a/BUILD.gn b/BUILD.gn index fb4a1b56e6..24decac129 100644 --- a/BUILD.gn +++ b/BUILD.gn @@ -459,6 +459,7 @@ optional("fontmgr_mac_ct") { # AppKit symbols NSFontWeightXXX may be dlsym'ed. "AppKit.framework", "ApplicationServices.framework", + "AVFoundation.framework", ] } @@ -2500,6 +2501,7 @@ if (skia_enable_tools) { test_app("viewer") { is_shared_library = is_android sources = [ + "src/ports/SkAudioPlayer.cpp", "tools/viewer/AnimTimer.h", "tools/viewer/BisectSlide.cpp", "tools/viewer/GMSlide.cpp", @@ -2552,6 +2554,11 @@ if (skia_enable_tools) { "//third_party/spirv-tools:spvtools_val", ] } + if (is_mac) { + sources += [ "src/ports/SkAudioPlayer_mac.mm" ] + } else { + sources += [ "src/ports/SkAudioPlayer_none.cpp" ] + } } } diff --git a/gn/samples.gni b/gn/samples.gni index 83da0f7abb..f1d65d15a3 100644 --- a/gn/samples.gni +++ b/gn/samples.gni @@ -22,6 +22,7 @@ samples_sources = [ "$_samplecode/SampleAnimatedText.cpp", "$_samplecode/SampleArc.cpp", "$_samplecode/SampleAtlas.cpp", + "$_samplecode/SampleAudio.cpp", "$_samplecode/SampleBackdropBounds.cpp", "$_samplecode/SampleBitmapRect.cpp", "$_samplecode/SampleCamera.cpp", diff --git a/include/ports/SkAudioPlayer.h b/include/ports/SkAudioPlayer.h new file mode 100644 index 0000000000..9fdd280799 --- /dev/null +++ b/include/ports/SkAudioPlayer.h @@ -0,0 +1,63 @@ +/* + * Copyright 2020 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ +#pragma once + +#include "include/core/SkData.h" +#include + +class SK_API SkAudioPlayer { +public: + virtual ~SkAudioPlayer(); + + // Returns null on failure (possibly unknown format?) + static std::unique_ptr Make(sk_sp); + + // in seconds + double duration() const { return this->onGetDuration(); } + double time() const { return this->onGetTime(); } // 0...duration() + double setTime(double); // returns actual time + + double normalizedTime() const { return this->time() / this->duration(); } + double setNormalizedTime(double t); + + enum class State { + kPlaying, + kStopped, + kPaused, + }; + State state() const { return fState; } + float volume() const { return fVolume; } // 0...1 + float rate() const { return fRate; } // multiplier (e.g. 1.0 is default) + + State setState(State); // returns actual State + float setRate(float); // returns actual rate + float setVolume(float); // returns actual volume + + void play() { this->setState(State::kPlaying); } + void pause() { this->setState(State::kPaused); } + void stop() { this->setState(State::kStopped); } + + bool isPlaying() const { return this->state() == State::kPlaying; } + bool isPaused() const { return this->state() == State::kPaused; } + bool isStopped() const { return this->state() == State::kStopped; } + +protected: + SkAudioPlayer() {} // only called by subclasses + + virtual double onGetDuration() const = 0; + virtual double onGetTime() const = 0; + virtual double onSetTime(double) = 0; + + virtual State onSetState(State) = 0; + virtual float onSetRate(float) = 0; + virtual float onSetVolume(float) = 0; + +private: + State fState = State::kStopped; + float fRate = 1.0f; + float fVolume = 1.0f; +}; diff --git a/samplecode/SampleAudio.cpp b/samplecode/SampleAudio.cpp new file mode 100644 index 0000000000..acc27ec3f2 --- /dev/null +++ b/samplecode/SampleAudio.cpp @@ -0,0 +1,95 @@ +/* + * Copyright 2020 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "include/core/SkCanvas.h" +#include "include/core/SkData.h" +#include "include/ports/SkAudioPlayer.h" +#include "samplecode/Sample.h" +#include "src/core/SkUtils.h" +#include "tools/Resources.h" + +class AudioView : public Sample { + std::unique_ptr fPlayer; + SkRect fBar; + +public: + AudioView() {} + +protected: + SkString name() override { return SkString("Audio"); } + + void onOnceBeforeDraw() override { + auto data = SkData::MakeFromFileName("/Users/reed/skia/mp3/scott-joplin-peacherine-rag.mp3"); + if (data) { + fPlayer = SkAudioPlayer::Make(data); + + SkDebugf("make: dur:%g time%g state:%d", + fPlayer->duration(), + fPlayer->time(), + fPlayer->state()); + } + + fBar = { 10, 10, 510, 30 }; + } + + void onDrawContent(SkCanvas* canvas) override { + if (!fPlayer) { + return; + } + + SkPaint p; + p.setColor(0xFFCCCCCC); + canvas->drawRect(fBar, p); + + p.setColor(fPlayer->isPlaying() ? SK_ColorBLUE : 0xFF8888FF); + SkRect r = fBar; + r.fRight = r.fLeft + (float)fPlayer->normalizedTime() * r.width(); + canvas->drawRect(r, p); + } + + bool onChar(SkUnichar c) override { + if (c == ' ') { + switch (fPlayer->state()) { + case SkAudioPlayer::State::kPlaying: fPlayer->pause(); break; + case SkAudioPlayer::State::kPaused: fPlayer->play(); break; + case SkAudioPlayer::State::kStopped: fPlayer->play(); break; + } + return true; + } + return this->INHERITED::onChar(c); + } + + Click* onFindClickHandler(SkScalar x, SkScalar y, skui::ModifierKey) override { + if (fPlayer && fBar.contains(x, y)) { + bool wasPlaying = fPlayer->isPlaying(); + if (wasPlaying) { + fPlayer->pause(); + } + return new Click([this, wasPlaying](Click* click) { + if (fBar.contains(click->fCurr.fX, click->fCurr.fY)) { + fPlayer->setNormalizedTime((click->fCurr.fX - fBar.fLeft) / fBar.width()); + } + + if (click->fState == skui::InputState::kUp) { + if (wasPlaying) { + fPlayer->play(); + } + } + return true; + }); + } + return nullptr; + } + + bool onAnimate(double /*nanos*/) override { + return true; + } + +private: + typedef Sample INHERITED; +}; +DEF_SAMPLE( return new AudioView; ) diff --git a/src/ports/SkAudioPlayer.cpp b/src/ports/SkAudioPlayer.cpp new file mode 100644 index 0000000000..a190521bfb --- /dev/null +++ b/src/ports/SkAudioPlayer.cpp @@ -0,0 +1,57 @@ +/* +* Copyright 2020 Google Inc. +* +* Use of this source code is governed by a BSD-style license that can be +* found in the LICENSE file. +*/ + +#include "include/ports/SkAudioPlayer.h" +#include +#include + +SkAudioPlayer::~SkAudioPlayer() {} + +double SkAudioPlayer::setTime(double t) { + t = std::min(std::max(t, 0.0), this->duration()); + if (!std::isfinite(t)) { + t = this->time(); + } + if (t != this->time()) { + t = this->onSetTime(t); + } + return t; +} + +double SkAudioPlayer::setNormalizedTime(double t) { + this->setTime(t * this->duration()); + return this->normalizedTime(); +} + +SkAudioPlayer::State SkAudioPlayer::setState(State s) { + if (s != fState) { + fState = this->onSetState(s); + } + return fState; +} + +float SkAudioPlayer::setRate(float r) { + r = std::min(std::max(r, 0.f), 1.f); + if (!std::isfinite(r)) { + r = fRate; + } + if (r != fRate) { + fRate = this->onSetRate(r); + } + return fRate; +} + +float SkAudioPlayer::setVolume(float v) { + v = std::min(std::max(v, 0.f), 1.f); + if (!std::isfinite(v)) { + v = fVolume; + } + if (v != fVolume) { + fVolume = this->onSetVolume(v); + } + return fVolume; +} diff --git a/src/ports/SkAudioPlayer_mac.mm b/src/ports/SkAudioPlayer_mac.mm new file mode 100644 index 0000000000..8a5ffc3517 --- /dev/null +++ b/src/ports/SkAudioPlayer_mac.mm @@ -0,0 +1,83 @@ +/* +* Copyright 2020 Google Inc. +* +* Use of this source code is governed by a BSD-style license that can be +* found in the LICENSE file. +*/ + +#include "include/core/SkData.h" +#include "include/ports/SkAudioPlayer.h" + +#if defined(SK_BUILD_FOR_MAC) || defined(SK_BUILD_FOR_IOS) + +#ifdef SK_BUILD_FOR_MAC +#include +#endif + +#ifdef SK_BUILD_FOR_IOS +// ??? +#endif + +class SkAudioPlayer_Mac : public SkAudioPlayer { +public: + SkAudioPlayer_Mac(AVAudioPlayer* player, sk_sp data) + : fPlayer(player) + , fData(std::move(data)) + { + fPlayer.enableRate = YES; + [fPlayer prepareToPlay]; + } + + ~SkAudioPlayer_Mac() override { + // [fPlayer release]; + } + + double onGetDuration() const override { return [fPlayer duration]; } + + double onGetTime() const override { return fPlayer.currentTime; } + + double onSetTime(double t) override { + bool wasPlaying = this->isPlaying(); + if (wasPlaying) { + [fPlayer pause]; + } + fPlayer.currentTime = t; + if (wasPlaying) { + [fPlayer play]; + } + return fPlayer.currentTime; + } + + + State onSetState(State state) override { + switch (state) { + case State::kPlaying: [fPlayer play]; break; + case State::kStopped: [fPlayer stop]; break; + case State::kPaused: [fPlayer pause]; break; + } + return state; + } + + float onSetRate(float r) override { fPlayer.rate = r; return r; } + + float onSetVolume(float v) override { fPlayer.volume = v; return v; } + +private: + AVAudioPlayer* fPlayer; + sk_sp fData; // we hold this onbehalf of the player's NSData +}; + +std::unique_ptr SkAudioPlayer::Make(sk_sp src) { + // The NSData does not own the actual buffer (src), but our subclass will + + NSData* data = [[NSData alloc] initWithBytesNoCopy:(void*)src->data() length:src->size()]; + AVAudioPlayer* player = [[AVAudioPlayer alloc] initWithData:data error:nil]; + [data release]; + + if (player) { + return std::unique_ptr(new SkAudioPlayer_Mac(player, std::move(src))); + } + return nullptr; +} + +#endif diff --git a/src/ports/SkAudioPlayer_none.cpp b/src/ports/SkAudioPlayer_none.cpp new file mode 100644 index 0000000000..79e0f4d666 --- /dev/null +++ b/src/ports/SkAudioPlayer_none.cpp @@ -0,0 +1,13 @@ +/* +* Copyright 2020 Google Inc. +* +* Use of this source code is governed by a BSD-style license that can be +* found in the LICENSE file. +*/ + +#include "include/core/SkData.h" +#include "include/ports/SkAudioPlayer.h" + +std::unique_ptr SkAudioPlayer::Make(sk_sp src) { + return nullptr; +}