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  }