Simple MJPEG streamer for ESPHome cameras.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

94 lines
1.6 KiB

2 years ago
  1. package main
  2. import (
  3. "bytes"
  4. "fmt"
  5. "sync"
  6. "maze.io/x/esphome"
  7. )
  8. // Camera stream.
  9. type Camera struct {
  10. esphome.Camera
  11. sync.RWMutex
  12. jpeg []byte
  13. frame []byte
  14. client map[chan []byte]struct{}
  15. }
  16. // NewCamera starts streaming from the ESPHome camera.
  17. func NewCamera(camera esphome.Camera) (*Camera, error) {
  18. c := &Camera{
  19. Camera: camera,
  20. client: make(map[chan []byte]struct{}),
  21. }
  22. stream, err := c.Stream()
  23. if err != nil {
  24. return nil, err
  25. }
  26. go c.stream(stream)
  27. return c, nil
  28. }
  29. const boundaryWord = "MJPEGBOUNDARY"
  30. func (c *Camera) stream(stream <-chan *bytes.Buffer) {
  31. for frame := range stream {
  32. const headerFormat = "\r\n" +
  33. "--" + boundaryWord + "\r\n" +
  34. "Content-Type: image/jpeg\r\n" +
  35. "Content-Length: %d\r\n" +
  36. "X-Timestamp: 0.000000\r\n" +
  37. "\r\n"
  38. var (
  39. jpeg = frame.Bytes()
  40. size = len(jpeg)
  41. header = fmt.Sprintf(headerFormat, size)
  42. )
  43. c.Lock()
  44. if len(c.jpeg) < size {
  45. c.jpeg = make([]byte, size)
  46. }
  47. copy(c.jpeg, jpeg)
  48. if len(c.frame) < size+len(header) {
  49. c.frame = make([]byte, (size+len(header))*2)
  50. }
  51. copy(c.frame, header)
  52. copy(c.frame[len(header):], jpeg)
  53. for client := range c.client {
  54. select {
  55. case client <- c.frame:
  56. default:
  57. }
  58. }
  59. c.Unlock()
  60. }
  61. }
  62. // Add a client stream.
  63. func (c *Camera) Add(client chan []byte) {
  64. c.Lock()
  65. c.client[client] = struct{}{}
  66. c.Unlock()
  67. }
  68. // Remove a client stream.
  69. func (c *Camera) Remove(client chan []byte) {
  70. c.Lock()
  71. delete(c.client, client)
  72. c.Unlock()
  73. }
  74. // JPEG returns the last received raw JPEG.
  75. func (c *Camera) JPEG() []byte {
  76. c.RLock()
  77. jpeg := make([]byte, len(c.jpeg))
  78. copy(jpeg, c.jpeg)
  79. c.RUnlock()
  80. return jpeg
  81. }