github.com/andrew00x/gomovies@v0.1.0/pkg/player/player_omxplayer.go (about) 1 package player 2 3 import ( 4 "errors" 5 "fmt" 6 "os" 7 "os/exec" 8 "syscall" 9 "time" 10 11 log "github.com/sirupsen/logrus" 12 13 "github.com/andrew00x/gomovies/pkg/api" 14 "github.com/andrew00x/gomovies/pkg/config" 15 "github.com/andrew00x/omxcontrol" 16 ) 17 18 type OMXPlayer struct { 19 process *os.Process 20 control *omxcontrol.OmxCtrl 21 listeners []PlayListener 22 muted bool 23 subtitlesOff bool 24 } 25 26 func init() { 27 playerFactory = func(conf *config.Config) (Player, error) { 28 return &OMXPlayer{}, nil 29 } 30 } 31 32 var controlNotSetup = errors.New("omxplayer does not play anything at the moment or control is not setup") 33 34 func (p *OMXPlayer) AddListener(l PlayListener) { 35 p.listeners = append(p.listeners, l) 36 } 37 38 func (p *OMXPlayer) AudioTracks() (audios []api.Stream, err error) { 39 var control *omxcontrol.OmxCtrl 40 if control, err = p.mustHaveControl(); err == nil { 41 var controlAudios []omxcontrol.Stream 42 controlAudios, err = control.AudioTracks() 43 if err == nil { 44 audios = convertToApiStreams(controlAudios) 45 } 46 } 47 return 48 } 49 50 func (p *OMXPlayer) NextAudioTrack() error { 51 return p.action(omxcontrol.ActionNextAudio) 52 } 53 54 func (p *OMXPlayer) NextSubtitle() error { 55 return p.action(omxcontrol.ActionNextSubtitle) 56 } 57 58 func (p *OMXPlayer) Pause() (err error) { 59 var control *omxcontrol.OmxCtrl 60 if control, err = p.mustHaveControl(); err == nil { 61 err = control.Pause() 62 } 63 return 64 } 65 66 func (p *OMXPlayer) Play() (err error) { 67 var control *omxcontrol.OmxCtrl 68 if control, err = p.mustHaveControl(); err == nil { 69 err = control.Play() 70 } 71 return 72 } 73 74 func (p *OMXPlayer) PlayMovie(path string) (err error) { 75 if stpErr := p.Stop(); stpErr != nil { 76 log.WithFields(log.Fields{"err": stpErr}).Error("Error occurred while stopping player") 77 } 78 err = p.start(path) 79 if err == nil { 80 var control *omxcontrol.OmxCtrl 81 control, err = setupControl() 82 if err == nil { 83 p.control = control 84 } else { 85 log.WithFields(log.Fields{"err": err}).Error("Error occurred while setup DBus connection to omxplayer") 86 if stpErr := p.Stop(); stpErr != nil { 87 log.WithFields(log.Fields{"err": stpErr}).Error("Error occurred while trying to stop player after unsuccessful start") 88 } 89 } 90 } 91 if err == nil { 92 for _, l := range p.listeners { 93 l.StartPlay(path) 94 } 95 go func() { 96 if _, waitErr := p.process.Wait(); waitErr != nil { 97 log.WithFields(log.Fields{"err": waitErr}).Error("Player process ended with error") 98 } 99 for _, l := range p.listeners { 100 l.StopPlay(path) 101 } 102 }() 103 } 104 return 105 } 106 107 func (p *OMXPlayer) PlayPause() (err error) { 108 var control *omxcontrol.OmxCtrl 109 if control, err = p.mustHaveControl(); err == nil { 110 err = control.PlayPause() 111 } 112 return 113 } 114 115 func (p *OMXPlayer) PreviousAudioTrack() error { 116 return p.action(omxcontrol.ActionPreviousAudio) 117 } 118 119 func (p *OMXPlayer) PreviousSubtitle() error { 120 return p.action(omxcontrol.ActionPreviousSubtitle) 121 } 122 123 func (p *OMXPlayer) ReplayCurrent() (err error) { 124 var control *omxcontrol.OmxCtrl 125 if control, err = p.mustHaveControl(); err == nil { 126 err = control.SetPosition(0) 127 } 128 return 129 } 130 131 func (p *OMXPlayer) Seek(offset time.Duration) (err error) { 132 var control *omxcontrol.OmxCtrl 133 if control, err = p.mustHaveControl(); err == nil { 134 err = control.Seek(offset) 135 } 136 return 137 } 138 139 func (p *OMXPlayer) SelectAudio(index int) (err error) { 140 var control *omxcontrol.OmxCtrl 141 if control, err = p.mustHaveControl(); err == nil { 142 var ok bool 143 if ok, err = control.SelectAudio(index); ok { 144 } else { 145 err = fmt.Errorf("audio track %d was not selected", index) 146 } 147 } 148 return 149 } 150 151 func (p *OMXPlayer) SelectSubtitle(index int) (err error) { 152 var control *omxcontrol.OmxCtrl 153 if control, err = p.mustHaveControl(); err == nil { 154 var ok bool 155 if ok, err = control.SelectSubtitle(index); ok { 156 } else { 157 err = fmt.Errorf("subtitle %d was not selected", index) 158 } 159 } 160 return 161 } 162 163 func (p *OMXPlayer) SetPosition(position time.Duration) (err error) { 164 var control *omxcontrol.OmxCtrl 165 if control, err = p.mustHaveControl(); err == nil { 166 err = control.SetPosition(position) 167 } 168 return 169 } 170 171 func (p *OMXPlayer) Status() (status api.PlayerStatus, err error) { 172 status.Stopped = true 173 control := p.control 174 if control != nil { 175 if ready, e := control.CanControl(); ready && e == nil { 176 var file string 177 var position, duration time.Duration 178 var pbs omxcontrol.Status 179 var audios []omxcontrol.Stream 180 var subs []omxcontrol.Stream 181 file, err = control.Playing() 182 if err != nil { 183 return 184 } 185 position, err = control.Position() 186 if err != nil { 187 return 188 } 189 duration, err = control.Duration() 190 if err != nil { 191 return 192 } 193 pbs, err = control.PlaybackStatus() 194 if err != nil { 195 return 196 } 197 audios, err = control.AudioTracks() 198 if err != nil { 199 return 200 } 201 subs, err = control.Subtitles() 202 if err == nil { 203 status.File = file 204 status.Position = int(position / time.Second) 205 status.Duration = int(duration / time.Second) 206 status.Paused = pbs == omxcontrol.Paused 207 status.Muted = p.muted 208 status.SubtitlesOff = p.subtitlesOff 209 status.ActiveAudioTrack = findActive(audios) 210 status.ActiveSubtitle = findActive(subs) 211 status.Stopped = false 212 } 213 } 214 } 215 return 216 } 217 218 func (p *OMXPlayer) Stop() error { 219 return p.quit() 220 } 221 222 func (p *OMXPlayer) Subtitles() (subtitles []api.Stream, err error) { 223 var control *omxcontrol.OmxCtrl 224 if control, err = p.mustHaveControl(); err == nil { 225 var controlSubtitles []omxcontrol.Stream 226 controlSubtitles, err = control.Subtitles() 227 if err == nil { 228 subtitles = convertToApiStreams(controlSubtitles) 229 } 230 } 231 return 232 } 233 234 func (p *OMXPlayer) ToggleMute() (err error) { 235 var control *omxcontrol.OmxCtrl 236 if control, err = p.mustHaveControl(); err == nil { 237 if p.muted { 238 err = control.Unmute() 239 } else { 240 err = control.Mute() 241 } 242 } 243 if err == nil { 244 p.muted = !p.muted 245 } 246 return 247 } 248 249 func (p *OMXPlayer) ToggleSubtitles() (err error) { 250 if err = p.action(omxcontrol.ActionToggleSubtitle); err == nil { 251 p.subtitlesOff = !p.subtitlesOff 252 } 253 return 254 } 255 256 func (p *OMXPlayer) Volume() (vol float64, err error) { 257 var control *omxcontrol.OmxCtrl 258 if control, err = p.mustHaveControl(); err == nil { 259 vol, err = control.Volume() 260 } 261 return 262 } 263 264 func (p *OMXPlayer) VolumeDown() error { 265 return p.action(omxcontrol.ActionDecreaseVolume) 266 } 267 268 func (p *OMXPlayer) VolumeUp() error { 269 return p.action(omxcontrol.ActionIncreaseVolume) 270 } 271 272 func (p *OMXPlayer) action(actionCode omxcontrol.KeyboardAction) (err error) { 273 var control *omxcontrol.OmxCtrl 274 if control, err = p.mustHaveControl(); err == nil { 275 err = control.Action(actionCode) 276 } 277 return 278 } 279 280 func (p *OMXPlayer) quit() (err error) { 281 process := p.process 282 if process != nil { 283 log.WithFields(log.Fields{"PID": process.Pid}).Info("kill omxplayer") 284 var pgid int 285 if pgid, err = syscall.Getpgid(process.Pid); err != nil { 286 return 287 } 288 if err = syscall.Kill(-pgid, syscall.SIGTERM); err != nil { 289 return 290 } 291 _, _ = process.Wait() 292 p.process = nil 293 p.control = nil 294 p.muted = false 295 p.subtitlesOff = false 296 } 297 return 298 } 299 300 func setupControl() (control *omxcontrol.OmxCtrl, err error) { 301 attempts := 10 302 retryDelay := time.Duration(2) * time.Second 303 var ready bool 304 for i := 1; ; i++ { 305 time.Sleep(retryDelay) 306 control, err = omxcontrol.Create() 307 if err != nil { 308 continue 309 } 310 ready, err = control.CanControl() 311 if err == nil && ready { 312 log.WithFields(log.Fields{"attempts": i}).Info("Setup omxplayer control") 313 return 314 } 315 if i > attempts { 316 break 317 } 318 } 319 err = fmt.Errorf("unable setup omxplayer control after %d attempts, last error: %v", attempts, err) 320 return 321 } 322 323 func (p *OMXPlayer) start(path string) (err error) { 324 cmd := exec.Command("/usr/bin/omxplayer", "-b", path) 325 cmd.SysProcAttr = &syscall.SysProcAttr{Setpgid: true} 326 err = cmd.Start() 327 if err == nil { 328 p.process = cmd.Process 329 log.WithFields(log.Fields{"PID": p.process.Pid, "file": path}).Info("Started omxplayer") 330 } 331 return 332 } 333 334 func (p *OMXPlayer) mustHaveControl() (control *omxcontrol.OmxCtrl, err error) { 335 control = p.control 336 if control == nil { 337 err = controlNotSetup 338 } 339 return 340 } 341 342 func findActive(streams []omxcontrol.Stream) int { 343 for _, stream := range streams { 344 if stream.Active { 345 return stream.Index 346 } 347 } 348 return -1 349 } 350 351 func convertToApiStreams(streams []omxcontrol.Stream) []api.Stream { 352 var apiStreams []api.Stream 353 for _, stream := range streams { 354 apiStreams = append(apiStreams, api.Stream{Index: stream.Index, Name: stream.Name, Language: stream.Language, Codec: stream.Codec, Active: stream.Active}) 355 } 356 return apiStreams 357 }