github.com/CyCoreSystems/ari@v4.8.4+incompatible/ext/record/record.go (about)

     1  package record
     2  
     3  import (
     4  	"context"
     5  	"fmt"
     6  	"strings"
     7  	"time"
     8  
     9  	"sync"
    10  
    11  	"github.com/CyCoreSystems/ari"
    12  	"github.com/CyCoreSystems/ari/rid"
    13  	"github.com/pkg/errors"
    14  )
    15  
    16  var (
    17  
    18  	// RecordingStartTimeout is the amount of time to wait for a recording to start
    19  	// before declaring the recording to have failed.
    20  	RecordingStartTimeout = 1 * time.Second
    21  
    22  	// DefaultMaximumDuration is the default maximum amount of time a recording
    23  	// should be allowed to continue before being terminated.
    24  	DefaultMaximumDuration = 24 * time.Hour
    25  
    26  	// DefaultMaximumSilence is the default maximum amount of time silence may be
    27  	// detected before terminating the recording.
    28  	DefaultMaximumSilence = 5 * time.Minute
    29  
    30  	// ShutdownGracePeriod is the amount of time to allow a Stop transaction to
    31  	// complete before shutting down the session anyway.
    32  	ShutdownGracePeriod = 3 * time.Second
    33  )
    34  
    35  // Options describes a set of recording options for a recording Session
    36  type Options struct {
    37  	// name is the name for the live recording
    38  	name string
    39  
    40  	format string
    41  
    42  	maxDuration time.Duration
    43  
    44  	maxSilence time.Duration
    45  
    46  	ifExists string
    47  
    48  	beep bool
    49  
    50  	terminateOn string
    51  }
    52  
    53  func defaultOptions() *Options {
    54  	return &Options{
    55  		beep:        false,
    56  		format:      "wav",
    57  		ifExists:    "fail",
    58  		maxDuration: DefaultMaximumDuration,
    59  		maxSilence:  DefaultMaximumSilence,
    60  		name:        rid.New(rid.Recording),
    61  		terminateOn: "none",
    62  	}
    63  }
    64  
    65  func (o *Options) toRecordingOptions() *ari.RecordingOptions {
    66  	return &ari.RecordingOptions{
    67  		Beep:        o.beep,
    68  		Format:      o.format,
    69  		Exists:      o.ifExists,
    70  		MaxDuration: o.maxDuration,
    71  		MaxSilence:  o.maxSilence,
    72  		Terminate:   o.terminateOn,
    73  	}
    74  }
    75  
    76  // Apply applies a set of options for the recording Session
    77  func (o *Options) Apply(opts ...OptionFunc) {
    78  	for _, f := range opts {
    79  		f(o)
    80  	}
    81  }
    82  
    83  // OptionFunc is a function which applies changes to an Options set
    84  type OptionFunc func(*Options)
    85  
    86  // Beep indicates that a beep should be played to signal the start of recording
    87  func Beep() OptionFunc {
    88  	return func(o *Options) {
    89  		o.beep = true
    90  	}
    91  }
    92  
    93  // Format configures the file format to be used to store the recording
    94  func Format(format string) OptionFunc {
    95  	return func(o *Options) {
    96  		o.format = format
    97  	}
    98  }
    99  
   100  // IfExists configures the behaviour of the recording if the file to be
   101  // recorded already exists.
   102  //
   103  // Valid options are:  "fail" (default), "overwrite", and "append".
   104  func IfExists(action string) OptionFunc {
   105  	return func(o *Options) {
   106  		o.ifExists = action
   107  	}
   108  }
   109  
   110  // MaxDuration sets the maximum duration to allow for the recording.  After
   111  // this amount of time, the recording will be automatically Finished.
   112  //
   113  // A setting of 0 disables the limit.
   114  func MaxDuration(max time.Duration) OptionFunc {
   115  	return func(o *Options) {
   116  		o.maxDuration = max
   117  	}
   118  }
   119  
   120  // MaxSilence sets the amount of time a block of silence is allowed to become
   121  // before the recording should be declared Finished.
   122  //
   123  // A setting of 0 disables silence detection.
   124  func MaxSilence(max time.Duration) OptionFunc {
   125  	return func(o *Options) {
   126  		o.maxSilence = max
   127  	}
   128  }
   129  
   130  // Name configures the recording to use the provided name
   131  func Name(name string) OptionFunc {
   132  	return func(o *Options) {
   133  		o.name = name
   134  	}
   135  }
   136  
   137  // TerminateOn configures the DTMF which, if received, will terminate the
   138  // recording.
   139  //
   140  // Valid values are "none" (default), "any", "*", and "#".
   141  func TerminateOn(dtmf string) OptionFunc {
   142  	return func(o *Options) {
   143  		o.terminateOn = dtmf
   144  	}
   145  }
   146  
   147  // Session desribes the interface to a generic recording session
   148  type Session interface {
   149  	// Done returns a channel which is closed when the session is complete
   150  	Done() <-chan struct{}
   151  
   152  	// Err waits for the session to complete, then returns any error encountered
   153  	// during its execution
   154  	Err() error
   155  
   156  	// Key returns the ari.Key for the LiveRecording of this session, if one exists.
   157  	Key() *ari.Key
   158  
   159  	// Pause temporarily stops the recording session without ending the session
   160  	Pause() error
   161  
   162  	// Result waits for the session to complete, then returns the Result
   163  	Result() (*Result, error)
   164  
   165  	// Resume restarts a paused recording session
   166  	Resume() error
   167  
   168  	// Scrap terminates the recording session and throws away the recording.
   169  	Scrap() error
   170  
   171  	// Stop stops the recording session
   172  	Stop() *Result
   173  }
   174  
   175  // Result represents the result of a recording Session.  It provides an
   176  // interface to disposition the recording.
   177  type Result struct {
   178  	h *ari.StoredRecordingHandle
   179  
   180  	// Data holds the final data for the LiveRecording, if it was successful
   181  	Data *ari.LiveRecordingData
   182  
   183  	// DTMF holds any DTMF digits which are received during the recording session
   184  	DTMF string
   185  
   186  	// Duration indicates the duration of the recording
   187  	Duration time.Duration
   188  
   189  	// Error holds any error encountered during the recording session
   190  	Error error
   191  
   192  	// Hangup indicates that the Recorder disappeared (due to hangup or
   193  	// destruction) during or after the recording.
   194  	Hangup bool
   195  
   196  	overwrite bool
   197  }
   198  
   199  // Delete discards the recording
   200  func (r *Result) Delete() error {
   201  	if r.h == nil {
   202  		return errors.New("no stored recording handle available")
   203  	}
   204  	return r.h.Delete()
   205  }
   206  
   207  // Key returns the ari.Key of the StoredRecording, if one exists.
   208  func (r *Result) Key() *ari.Key {
   209  	if r == nil || r.h == nil {
   210  		return nil
   211  	}
   212  	return r.h.Key()
   213  }
   214  
   215  // Save stores the recording to the given name
   216  func (r *Result) Save(name string) error {
   217  	if name == "" {
   218  		// no name indicates the default, which is where it already is
   219  		return nil
   220  	}
   221  
   222  	if r.h == nil {
   223  		return errors.New("no stored recording handle available")
   224  	}
   225  
   226  	// Copy the recording to the desired name
   227  	destH, err := r.h.Copy(name)
   228  	if err != nil {
   229  		if !strings.Contains(err.Error(), "409 Conflict") || !r.overwrite {
   230  			return errors.Wrapf(err, "failed to copy recording (%s)", r.h.ID())
   231  		}
   232  
   233  		// we are set to overwrite, so delete the previous recording
   234  		Logger.Debug("overwriting previous recording")
   235  		err = destH.Delete()
   236  		if err != nil {
   237  			return errors.Wrap(err, "failed to remove previous destination recording")
   238  		}
   239  		_, err = r.h.Copy(name)
   240  		if err != nil {
   241  			return errors.Wrap(err, "failed to copy recording")
   242  		}
   243  	}
   244  
   245  	// Delete the original
   246  	err = r.h.Delete()
   247  	if err != nil {
   248  		return errors.Wrap(err, "failed to remove temporary recording after copy")
   249  	}
   250  
   251  	return nil
   252  }
   253  
   254  // URI returns the AudioURI to play the recording
   255  func (r *Result) URI() string {
   256  	return "recording:" + r.h.ID()
   257  }
   258  
   259  // Record starts a new recording Session
   260  func Record(ctx context.Context, r ari.Recorder, opts ...OptionFunc) Session {
   261  	s := newRecordingSession(opts...)
   262  
   263  	var wg sync.WaitGroup
   264  	wg.Add(1)
   265  	go s.record(ctx, r, &wg)
   266  	wg.Wait()
   267  
   268  	Logger.Debug("returned from internal recording start")
   269  	return s
   270  }
   271  
   272  // New creates a new recording Session
   273  func newRecordingSession(opts ...OptionFunc) *recordingSession {
   274  	o := defaultOptions()
   275  	o.Apply(opts...)
   276  
   277  	s := &recordingSession{
   278  		cancel:  func() {},
   279  		options: o,
   280  		doneCh:  make(chan struct{}),
   281  		res:     new(Result),
   282  	}
   283  
   284  	// If the recording options declare that we should overwrite,
   285  	// carry that over to the copy destination.
   286  	if o.ifExists == "overwrite" {
   287  		s.res.overwrite = true
   288  	}
   289  
   290  	return s
   291  }
   292  
   293  type recordingSession struct {
   294  	h *ari.LiveRecordingHandle
   295  
   296  	cancel context.CancelFunc
   297  
   298  	doneCh chan struct{}
   299  
   300  	options *Options
   301  
   302  	res *Result
   303  }
   304  
   305  func (s *recordingSession) Done() <-chan struct{} {
   306  	return s.doneCh
   307  }
   308  
   309  func (s *recordingSession) Err() error {
   310  	<-s.Done()
   311  	return s.res.Error
   312  }
   313  
   314  func (s *recordingSession) Key() *ari.Key {
   315  	if s == nil || s.h == nil {
   316  		return nil
   317  	}
   318  	return s.h.Key()
   319  }
   320  
   321  func (s *recordingSession) Result() (*Result, error) {
   322  	<-s.Done()
   323  	return s.res, s.res.Error
   324  }
   325  
   326  func (s *recordingSession) Pause() error {
   327  	return s.h.Pause()
   328  }
   329  
   330  func (s *recordingSession) Resume() error {
   331  	return s.h.Resume()
   332  }
   333  
   334  func (s *recordingSession) Scrap() error {
   335  	return s.h.Scrap()
   336  }
   337  
   338  func (s *recordingSession) Stop() *Result {
   339  	// Signal stop
   340  	s.res.Error = s.h.Stop()
   341  
   342  	// If we successfully signaled a stop, Wait for the stop to complete
   343  	if s.res.Error == nil {
   344  		select {
   345  		case <-s.Done():
   346  		case <-time.After(ShutdownGracePeriod):
   347  		}
   348  	}
   349  
   350  	// Shut down the session
   351  	if s.cancel != nil {
   352  		s.cancel()
   353  	}
   354  
   355  	// Return the result
   356  	return s.res
   357  }
   358  
   359  // nolint: gocyclo
   360  func (s *recordingSession) record(ctx context.Context, r ari.Recorder, wg *sync.WaitGroup) {
   361  	defer close(s.doneCh)
   362  
   363  	ctx, cancel := context.WithCancel(ctx)
   364  	s.cancel = cancel
   365  	defer cancel()
   366  
   367  	var err error
   368  	s.h, err = r.StageRecord(s.options.name, s.options.toRecordingOptions())
   369  	if err != nil {
   370  		s.res.Error = errors.Wrap(err, "failed to stage recording")
   371  		wg.Done()
   372  		return
   373  	}
   374  
   375  	// Store the eventual StoredRecording handle to the Result
   376  	s.res.h = s.h.Stored()
   377  
   378  	dtmfSub := r.Subscribe(ari.Events.ChannelDtmfReceived)
   379  	hangupSub := r.Subscribe(ari.Events.ChannelDestroyed, ari.Events.ChannelHangupRequest, ari.Events.BridgeDestroyed)
   380  	startSub := s.h.Subscribe(ari.Events.RecordingStarted)
   381  	failedSub := s.h.Subscribe(ari.Events.RecordingFailed)
   382  	finishedSub := s.h.Subscribe(ari.Events.RecordingFinished)
   383  
   384  	defer func() {
   385  		hangupSub.Cancel()
   386  		failedSub.Cancel()
   387  		startSub.Cancel()
   388  		finishedSub.Cancel()
   389  		dtmfSub.Cancel()
   390  	}()
   391  
   392  	wg.Done()
   393  
   394  	// Record the duration of the recording
   395  	started := time.Now()
   396  	defer func() {
   397  		s.res.Duration = time.Since(started)
   398  		Logger.Debug("recording duration", "duration", s.res.Duration)
   399  	}()
   400  
   401  	// Record any DTMF received during the recording
   402  	go s.collectDtmf(ctx, dtmfSub)
   403  
   404  	// Record hangup or destruction of our Recorder
   405  	go s.watchHangup(ctx, hangupSub)
   406  
   407  	// Start recording
   408  	Logger.Debug("starting recording")
   409  	if err := s.h.Exec(); err != nil {
   410  		s.res.Error = err
   411  		return
   412  	}
   413  
   414  	// Time the recording
   415  	startTimer := time.NewTimer(RecordingStartTimeout)
   416  
   417  	for {
   418  		select {
   419  		case <-ctx.Done():
   420  			s.Stop()
   421  			return
   422  		case <-startTimer.C:
   423  			Logger.Debug("timeout waiting to start recording")
   424  			s.res.Error = timeoutErr{"Timeout waiting for recording to start"}
   425  			return
   426  		case _, ok := <-startSub.Events():
   427  			if !ok {
   428  				return
   429  			}
   430  			Logger.Debug("recording started")
   431  			startTimer.Stop()
   432  		case e, ok := <-failedSub.Events():
   433  			if !ok {
   434  				return
   435  			}
   436  			Logger.Debug("recording failed")
   437  			r := e.(*ari.RecordingFailed).Recording
   438  			s.res.Data = &r
   439  			s.res.Error = fmt.Errorf("Recording failed: %s", r.Cause)
   440  			return
   441  		case e, ok := <-finishedSub.Events():
   442  			if !ok {
   443  				return
   444  			}
   445  			r := e.(*ari.RecordingFinished).Recording
   446  			Logger.Debug("recording finished")
   447  			s.res.Data = &r
   448  			return
   449  		}
   450  	}
   451  }
   452  
   453  func (s *recordingSession) collectDtmf(ctx context.Context, dtmfSub ari.Subscription) {
   454  	for {
   455  		select {
   456  		case e, ok := <-dtmfSub.Events():
   457  			if !ok {
   458  				return
   459  			}
   460  			v := e.(*ari.ChannelDtmfReceived)
   461  			s.res.DTMF += v.Digit
   462  		case <-ctx.Done():
   463  			return
   464  		}
   465  	}
   466  }
   467  
   468  func (s *recordingSession) watchHangup(ctx context.Context, hangupSub ari.Subscription) {
   469  	select {
   470  	case <-hangupSub.Events():
   471  		s.res.Hangup = true
   472  	case <-ctx.Done():
   473  	}
   474  }
   475  
   476  type timeoutErr struct {
   477  	msg string
   478  }
   479  
   480  func (err timeoutErr) Error() string {
   481  	return err.msg
   482  }
   483  
   484  func (err timeoutErr) Timeout() bool {
   485  	return true
   486  }