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 }