Skip to content
This repository has been archived by the owner on Mar 18, 2020. It is now read-only.

Commit

Permalink
switch to stream-based audio
Browse files Browse the repository at this point in the history
fixes #32
  • Loading branch information
Tim Cooper committed Dec 11, 2015
1 parent 15c3834 commit 62075c4
Show file tree
Hide file tree
Showing 4 changed files with 133 additions and 73 deletions.
44 changes: 28 additions & 16 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -75,33 +75,45 @@ piepan is built using the [gumble](https://github.com/layeh/gumble) library. Doc

### `piepan.Audio`

- `void Play(table obj)`: The following modes are supported:
- [`VoiceTarget`](https://godoc.org/github.com/layeh/gumble/gumble#VoiceTarget) `NewTarget(int id)`: Create a new voice target object.
- `piepan.AudioStream New(table obj)`: Returns a new AudioStream. The following modes are supported:
- Filename:
- Plays the media file `obj.filename`.
- Pipe:
- Uses the output of the program `obj.exec` executed with `obj.args`.

`obj.offset` defines the number of seconds from the beginning of the stream to starting playing at.
`obj.callback` can be defined as a function that is called after the playback has completed.
- [`VoiceTarget`](https://godoc.org/github.com/layeh/gumble/gumble#VoiceTarget) `NewTarget(int id)`: Create a new voice target object.
If audio has been paused
- `piepan.AudioStream Current()`: Returns the currently playing stream.
- `void SetTarget(VoiceTarget target)` sets the target of subsequent `piepan.Audio.Play()` calls. Call this function with no arguments to remove any voice targeting.
- `void Stop()`: Stops the currently playing stream.
- `bool IsPlaying()`: Returns if an stream is currently playing.
- `float Elapsed()` Returns the amount of audio (in seconds) that the current or most recent stream played.
- `int Bitrate()`: Returns the bitrate of the audio encoder.
- `void SetBitrate(int bitrate)`: Sets the bitrate of the audio encoder. Calling this function will override the automatically-configured, optimal bitrate.
- `float Volume()`: Returns the audio volume.
- `bool IsPlaying()`: Returns if there is a stream currently playing.

### `piepan.AudioStream`

Note: after an audio stream has stopped/completed, it cannot be started again.

- `void Play()`: Starts playing or resumes the audio stream.
- `void Stop()`: Stops a playing or paused stream.
- `void Pause()`: Pauses the playing stream.
- `void IsPlaying()`: Returns if the stream is playing.
- `void IsPaused()`: Returns if the stream is paused.
- `void IsStopped()`: Returns if the stream has stopped.
- `float Elapsed()`: Returns the amount of audio (in seconds) that the stream played.
- `void SetVolume(float volume)`: Sets the volume of transmitted audio (default: 1.0).
- `float Volume()`: Returns the stream's volume.

#### [`Channels`](https://godoc.org/github.com/layeh/gumble/gumble#Channels) `piepan.Channels`
### [`Channels`](https://godoc.org/github.com/layeh/gumble/gumble#Channels) `piepan.Channels`

Object that contains all of the channels that are on the server. The channels are mapped by their channel IDs. `piepan.Channels[0]` is the server's root channel.

#### `piepan.Disconnect()`
### `piepan.Disconnect()`

Disconnects from the server.

#### `piepan.On(string event, function callback)`
### `piepan.On(string event, function callback)`

Registers an event listener for a given event type. The follow events are currently supported:

Expand Down Expand Up @@ -167,33 +179,33 @@ Note: events with a `Type` field have slight changes than what is documented in
- `IsChannelFull`
- `IsNestingLimit`

#### `piepan.Process`
### `piepan.Process`

- `piepan.Process New(function callback, string command, string arguments...)`: Executes `command` in a new process with the given arguments. The function `callback` is executed once the process has completed, passing if the execution was successful and the contents of standard output.

- `void Kill()`: Kills the process.

#### [`User`](https://godoc.org/github.com/layeh/gumble/gumble#User) `piepan.Self`
### [`User`](https://godoc.org/github.com/layeh/gumble/gumble#User) `piepan.Self`

The `User` object that references yourself.

#### `piepan.Timer`
### `piepan.Timer`

- `piepan.Timer New(function callback, int timeout)`: Creates a new timer. After at least `timeout` milliseconds, `callback` will be executed.

- `void Cancel()`: Cancels the timer.

#### [`Users`](https://godoc.org/github.com/layeh/gumble/gumble#Users) `piepan.Users`
### [`Users`](https://godoc.org/github.com/layeh/gumble/gumble#Users) `piepan.Users`

Object containing each connected user on the server, with the keys being the session ID of the user and the value being their corresponding `piepan.User` table.

## Changelog

- 0.8.0 (Next)
- Switch to stream-based audio. Individual audios streams can be created then played, paused, and stopped.
- Add `gumble.ConnectEvent` wrapper
- Add pipe support to `piepan.Audio.Play`
- Add `offset` field to `piepan.Audio.Play`'s argument
- Add `piepan.Audio.Elapsed`
- Add pipe support to `piepan.Audio.New`
- Add `offset` field to `piepan.Audio.New`'s argument
- Remove all plugins; piepan is Lua only
- 0.7.0 (2015-04-08)
- Add additional Lua support via [gopher-lua](https://github.com/yuin/gopher-lua)
Expand Down
18 changes: 14 additions & 4 deletions _examples/soundboard.lua
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ do
local require_registered = true

-- Boolean if sounds should stop playing when another is triggered
local interrupt_sounds = false
local interrupt_sounds = true

-- Boolean if the bot should move into the user's channel to play the sound
local should_move = false
Expand Down Expand Up @@ -58,9 +58,19 @@ piepan.On("message", function(e)
end
piepan.Self:Move(e.Sender.Channel)
end
piepan.Audio.Play({
filename = soundFile
})
local current
if piepan.Audio.IsPlaying() then
current = piepan.Audio.Current()
current:Pause()
end
piepan.Audio.New({
filename = soundFile,
callback = function()
if current ~= nil then
current:Play()
end
end
}):Play()
end)

end
131 changes: 86 additions & 45 deletions apiaudio.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package piepan

import (
"sync"
"time"

"github.com/layeh/gopus"
Expand All @@ -10,7 +11,75 @@ import (
"github.com/yuin/gopher-lua"
)

func (s *State) apiAudioPlay(tbl *lua.LTable) bool {
type audioStream struct {
state *State
s *gumbleffmpeg.Stream
callback lua.LValue
wg sync.WaitGroup
}

func (a *audioStream) Volume() float32 {
return a.s.Volume
}

func (a *audioStream) SetVolume(volume float32) {
a.s.Volume = volume
}

func (a *audioStream) IsPlaying() bool {
return a.s.State() == gumbleffmpeg.StatePlaying
}

func (a *audioStream) Play() {
a.state.streamMu.Lock()
if a.state.stream != nil {
a.state.streamMu.Unlock()
return
}
a.state.stream = a
a.state.streamMu.Unlock()
a.wg.Add(1)
a.s.Play()
go func() {
a.s.Wait()
a.state.streamMu.Lock()
a.state.stream = nil
a.state.streamMu.Unlock()
a.wg.Done()
if a.callback.Type() != lua.LTNil {
a.state.callValue(a.callback)
}
}()
}

func (a *audioStream) IsStopped() bool {
return a.s.State() == gumbleffmpeg.StateStopped
}

func (a *audioStream) Stop() {
a.s.Stop()
a.wg.Wait()
}

func (a *audioStream) IsPaused() bool {
return a.s.State() == gumbleffmpeg.StatePaused
}

func (a *audioStream) Pause() {
a.state.streamMu.Lock()
defer a.state.streamMu.Unlock()
if a.state.stream != a {
return
}
a.state.stream = nil
a.s.Pause()
}

func (a *audioStream) Elapsed() float64 {
return a.s.Elapsed().Seconds()
}

func (s *State) apiAudioNew(tbl *lua.LTable) *audioStream {
filename := tbl.RawGetString("filename")

exec := tbl.RawGetString("exec")
Expand All @@ -19,10 +88,6 @@ func (s *State) apiAudioPlay(tbl *lua.LTable) bool {
offset := tbl.RawGetString("offset")
callback := tbl.RawGetString("callback")

if s.audioStream != nil {
s.audioStream.Stop()
}

if enc, ok := s.Client.AudioEncoder.(*opus.Encoder); ok {
enc.SetApplication(gopus.Audio)
}
Expand All @@ -49,19 +114,17 @@ func (s *State) apiAudioPlay(tbl *lua.LTable) bool {
panic("invalid piepan.Audio.Play source type")
}

s.audioStream = gumbleffmpeg.New(s.Client, source)
stream := &audioStream{
state: s,
s: gumbleffmpeg.New(s.Client, source),
callback: callback,
}

if number, ok := offset.(lua.LNumber); ok {
s.audioStream.Offset = time.Second * time.Duration(number)
stream.s.Offset = time.Second * time.Duration(number)
}
s.audioStream.Play()
go func(stream *gumbleffmpeg.Stream) {
stream.Wait()
if callback.Type() != lua.LTNil {
s.callValue(callback)
}
}(s.audioStream)

return true
return stream
}

func (s *State) apiAudioNewTarget(id uint32) *gumble.VoiceTarget {
Expand All @@ -83,20 +146,6 @@ func (s *State) apiAudioSetBitrate(bitrate int) {
}
}

func (s *State) apiAudioVolume() float32 {
if s.audioStream != nil {
return s.audioStream.Volume
}
return s.audioVolume
}

func (s *State) apiAudioSetVolume(volume float32) {
s.audioVolume = volume
if s.audioStream != nil {
s.audioStream.Volume = volume
}
}

func (s *State) apiAudioSetTarget(target ...*gumble.VoiceTarget) {
if len(target) == 0 {
s.Client.VoiceTarget = nil
Expand All @@ -107,22 +156,14 @@ func (s *State) apiAudioSetTarget(target ...*gumble.VoiceTarget) {
s.Client.VoiceTarget = target[0]
}

func (s *State) apiAudioStop() {
if s.audioStream != nil {
s.audioStream.Stop()
}
}

func (s *State) apiAudioElapsed() float64 {
if s.audioStream == nil {
return 0
}
return s.audioStream.Elapsed().Seconds()
func (s *State) apiAudioIsPlaying() bool {
s.streamMu.Lock()
defer s.streamMu.Unlock()
return s.stream != nil && s.stream.IsPlaying()
}

func (s *State) apiAudioIsPlaying() bool {
if s.audioStream == nil {
return false
}
return s.audioStream.State() == gumbleffmpeg.StatePlaying
func (s *State) apiAudioCurrent() *audioStream {
s.streamMu.Lock()
defer s.streamMu.Unlock()
return s.stream
}
13 changes: 5 additions & 8 deletions piepan.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@ import (

"github.com/layeh/gopher-luar"
"github.com/layeh/gumble/gumble"
"github.com/layeh/gumble/gumbleffmpeg"
"github.com/yuin/gopher-lua"
)

Expand All @@ -17,10 +16,11 @@ type State struct {
LState *lua.LState
table *lua.LTable

audioStream *gumbleffmpeg.Stream
audioVolume float32
AudioCommand string

streamMu sync.Mutex
stream *audioStream

mu sync.Mutex
listeners map[string][]lua.LValue
}
Expand All @@ -39,16 +39,13 @@ func New(client *gumble.Client) *State {
l.SetGlobal("piepan", t)
{
s := l.NewTable()
s.RawSetString("Play", luar.New(l, state.apiAudioPlay))
s.RawSetString("Elapsed", luar.New(l, state.apiAudioElapsed))
s.RawSetString("New", luar.New(l, state.apiAudioNew))
s.RawSetString("IsPlaying", luar.New(l, state.apiAudioIsPlaying))
s.RawSetString("Stop", luar.New(l, state.apiAudioStop))
s.RawSetString("Current", luar.New(l, state.apiAudioCurrent))
s.RawSetString("NewTarget", luar.New(l, state.apiAudioNewTarget))
s.RawSetString("SetTarget", luar.New(l, state.apiAudioSetTarget))
s.RawSetString("Bitrate", luar.New(l, state.apiAudioBitrate))
s.RawSetString("SetBitrate", luar.New(l, state.apiAudioSetBitrate))
s.RawSetString("Volume", luar.New(l, state.apiAudioVolume))
s.RawSetString("SetVolume", luar.New(l, state.apiAudioSetVolume))
t.RawSetString("Audio", s)
}
{
Expand Down

0 comments on commit 62075c4

Please sign in to comment.