diff --git a/pkg/avfoundation/avfoundation_darwin.go b/pkg/avfoundation/avfoundation_darwin.go index a7e1031..c9c5155 100644 --- a/pkg/avfoundation/avfoundation_darwin.go +++ b/pkg/avfoundation/avfoundation_darwin.go @@ -136,11 +136,26 @@ func (rc *ReadCloser) dataCb(data []byte) { // - For video, each call will return a frame. // - For audio, each call will return a chunk which its size configured by Latency func (rc *ReadCloser) Read() ([]byte, func(), error) { - data, ok := <-rc.dataChan - if !ok { - return nil, func() {}, io.EOF + data, release, err := rc.ReadContext(context.Background()) + if err != nil { + return nil, release, err + } + return data, release, nil +} + +// ReadContext is Read but with a context for better error handling e.g. timeout, cancellation, etc. +func (rc *ReadCloser) ReadContext(ctx context.Context) ([]byte, func(), error) { + release := func() {} // no-op to satisfy Reader interface + + select { + case <-ctx.Done(): + return nil, release, ctx.Err() + case data, ok := <-rc.dataChan: + if !ok { + return nil, release, io.EOF + } + return data, release, nil } - return data, func() {}, nil } // Close closes the capturing session, and no data will flow anymore diff --git a/pkg/driver/camera/camera_darwin.go b/pkg/driver/camera/camera_darwin.go index 7f37b68..5acf66f 100644 --- a/pkg/driver/camera/camera_darwin.go +++ b/pkg/driver/camera/camera_darwin.go @@ -1,7 +1,11 @@ package camera import ( + "context" + "errors" "image" + "io" + "time" "github.com/pion/mediadevices/pkg/avfoundation" "github.com/pion/mediadevices/pkg/driver" @@ -14,8 +18,11 @@ type camera struct { device avfoundation.Device session *avfoundation.Session rcClose func() + cancel context.CancelFunc } +const readTimeout = 3 * time.Second + func init() { Initialize() } @@ -53,6 +60,11 @@ func (cam *camera) Close() error { if cam.rcClose != nil { cam.rcClose() } + + if cam.cancel != nil { + cam.cancel() + } + return cam.session.Close() } @@ -66,10 +78,24 @@ func (cam *camera) VideoRecord(property prop.Media) (video.Reader, error) { if err != nil { return nil, err } + + ctx, cancel := context.WithCancel(context.Background()) + cam.cancel = cancel cam.rcClose = rc.Close r := video.ReaderFunc(func() (image.Image, func(), error) { - frame, _, err := rc.Read() + if ctx.Err() != nil { + // Return EOF if the camera is already closed. + return nil, func() {}, io.EOF + } + + readCtx, cancel := context.WithTimeout(ctx, readTimeout) + defer cancel() + + frame, _, err := rc.ReadContext(readCtx) if err != nil { + if errors.Is(err, context.DeadlineExceeded) || errors.Is(err, context.Canceled) { + return nil, func() {}, io.EOF + } return nil, func() {}, err } return decoder.Decode(frame, property.Width, property.Height)