1

The scenario is I have to start n number of rtsp camera stream using ffmpeg command. The user has option to start number of streams and can form tiler, so he can see many streaming cameras similar to (NVR live view).

I can start and stop stream using os/exec command with context and ffmpeg streaming command with it (which start's stream).

My question is is "how can I cancel a particular stream based on user's request" (in Python I can kill OS process using process ID). Is there any way to stop/start particular stream, any references will be helpful.

Brits
  • 14,829
  • 2
  • 18
  • 31
Nitin
  • 29
  • 5
  • [cmd.Process](https://pkg.go.dev/os/exec#Cmd.Process).[Signal](https://pkg.go.dev/os#Process.Signal) – Peter May 30 '23 at 09:57

1 Answers1

0

You can store the context.CancelFunc in a map and use it to cancel the context, which will in turn cause the child process to be terminated.

See the demo below:

main.go:

package main

import (
    "context"
    "log"
    "os"
    "os/exec"
    "os/signal"
    "sync"

    "github.com/gofiber/fiber/v2"
    "github.com/gofiber/fiber/v2/utils"
)

type manager struct {
    wg sync.WaitGroup

    mu   sync.Mutex
    jobs map[string]context.CancelFunc
}

func newManger() *manager {
    return &manager{
        jobs: make(map[string]context.CancelFunc),
    }
}

func (m *manager) startJob(ctx context.Context, id string) {
    log.Println("start job:", id)
    m.mu.Lock()
    defer m.mu.Unlock()

    if _, ok := m.jobs[id]; ok {
        return
    }

    ctx, cancel := context.WithCancel(ctx)
    cmd := exec.CommandContext(ctx, "./do.sh", id)
    cmd.Stdout = os.Stdout
    cmd.Stderr = os.Stderr

    m.wg.Add(1)
    go func() {
        defer m.wg.Done()
        err := cmd.Run()
        log.Printf("job: %s done. error: %v", id, err)

        m.mu.Lock()
        defer m.mu.Unlock()
        delete(m.jobs, id)
    }()

    m.jobs[id] = cancel
}

func (m *manager) stopJob(id string) {
    log.Println("stop job:", id)
    m.mu.Lock()
    defer m.mu.Unlock()

    if cancel, ok := m.jobs[id]; ok {
        cancel()
        return
    }
}

func main() {
    ctx, stop := signal.NotifyContext(context.Background(), os.Interrupt)
    defer stop()

    m := newManger()

    app := fiber.New()

    app.Post("/start/:id", func(c *fiber.Ctx) error {
        id := utils.CopyString(c.Params("id"))
        if id == "" {
            return nil
        }

        m.startJob(ctx, id)

        return nil
    })
    app.Post("/stop/:id", func(c *fiber.Ctx) error {
        id := utils.CopyString(c.Params("id", ""))
        if id == "" {
            return nil
        }
        m.stopJob(id)
        return nil
    })

    go func() {
        err := app.Listen(":3000")
        if err != nil {
            log.Println(err)
        }
    }()

    <-ctx.Done()
    _ = app.Shutdown()
    m.wg.Wait()
}

do.sh (chmod +x do.sh):

#!/usr/bin/env bash

while :; do
    echo $1
    sleep 1s
done

Usage:

  • send a request to start a new job:

    $ curl -X POST 'http://localhost:3000/start/1'
    
  • send a request to stop a running job:

    $ curl -X POST 'http://localhost:3000/stop/1'
    

Note: this is only a simple demo to show the idea. It's not fully tested. Use with caution.

Zeke Lu
  • 6,349
  • 1
  • 17
  • 23