3 from enigma import eTimer, iPlayableService, eServiceCenter, iServiceInformation, eSize
4 from Screens.Screen import Screen
5 from Screens.MessageBox import MessageBox
6 from Screens.InputBox import InputBox
7 from Components.ActionMap import NumberActionMap, ActionMap, HelpableActionMap
8 from Components.Label import Label
9 from Components.Input import Input
10 from Components.Pixmap import Pixmap
11 from Components.Label import Label
12 from Components.FileList import FileEntryComponent, FileList
13 from Components.MediaPlayer import PlayList, PlaylistEntryComponent
14 from Plugins.Plugin import PluginDescriptor
15 from Tools.Directories import resolveFilename, SCOPE_MEDIA, SCOPE_CONFIG, SCOPE_PLAYLIST, SCOPE_SKIN_IMAGE
16 from Components.ServicePosition import ServicePositionGauge
17 from Components.ServiceEventTracker import ServiceEventTracker
18 from Components.Playlist import PlaylistIOInternal, PlaylistIOM3U, PlaylistIOPLS
19 from Screens.InfoBarGenerics import InfoBarSeek, InfoBarAudioSelection, InfoBarCueSheetSupport, InfoBarNotifications
20 from ServiceReference import ServiceReference
21 from Screens.ChoiceBox import ChoiceBox
22 from Screens.HelpMenu import HelpableScreen
23 from bisect import insort
26 class MyPlayList(PlayList):
28 PlayList.__init__(self)
30 def PlayListShuffle(self):
31 random.shuffle(self.list)
32 self.l.setList(self.list)
34 self.oldCurrPlaying = -1
36 class MediaPlayer(Screen, InfoBarSeek, InfoBarAudioSelection, InfoBarCueSheetSupport, InfoBarNotifications, HelpableScreen):
38 ENABLE_RESUME_SUPPORT = True
40 def __init__(self, session, args = None):
41 Screen.__init__(self, session)
42 InfoBarAudioSelection.__init__(self)
43 InfoBarCueSheetSupport.__init__(self, actionmap = "MediaPlayerCueSheetActions")
44 InfoBarNotifications.__init__(self)
45 HelpableScreen.__init__(self)
47 self.oldService = self.session.nav.getCurrentlyPlayingServiceReference()
48 self.session.nav.stopService()
50 self.playlistparsers = {}
51 self.addPlaylistParser(PlaylistIOM3U, "m3u")
52 self.addPlaylistParser(PlaylistIOPLS, "pls")
53 self.addPlaylistParser(PlaylistIOInternal, "e2pls")
55 # 'None' is magic to start at the list of mountpoints
56 self.filelist = FileList(None, matchingPattern = "(?i)^.*\.(mp3|ogg|ts|wav|wave|m3u|pls|e2pls|mpg|vob)", useServiceRef = True)
57 self["filelist"] = self.filelist
59 self.playlist = MyPlayList()
60 #self.playlist = PlayList()
61 self.is_closing = False
63 self["playlist"] = self.playlist
65 self["PositionGauge"] = ServicePositionGauge(self.session.nav)
67 self["currenttext"] = Label("")
69 self["artisttext"] = Label(_("Artist:"))
70 self["artist"] = Label("")
71 self["titletext"] = Label(_("Title:"))
72 self["title"] = Label("")
73 self["albumtext"] = Label(_("Album:"))
74 self["album"] = Label("")
75 self["yeartext"] = Label(_("Year:"))
76 self["year"] = Label("")
77 self["genretext"] = Label(_("Genre:"))
78 self["genre"] = Label("")
79 self["coverArt"] = Pixmap()
81 self.seek_target = None
83 class MoviePlayerActionMap(NumberActionMap):
84 def __init__(self, player, contexts = [ ], actions = { }, prio=0):
85 NumberActionMap.__init__(self, contexts, actions, prio)
88 def action(self, contexts, action):
90 return NumberActionMap.action(self, contexts, action)
93 self["OkCancelActions"] = HelpableActionMap(self, "OkCancelActions",
95 "ok": (self.ok, _("add file to playlist")),
96 "cancel": (self.exit, _("exit mediaplayer")),
99 self["MediaPlayerActions"] = HelpableActionMap(self, "MediaPlayerActions",
101 "play": (self.playEntry, _("play entry")),
102 "pause": (self.pauseEntry, _("pause")),
103 "stop": (self.stopEntry, _("stop entry")),
104 "previous": (self.previousEntry, _("play previous playlist entry")),
105 "next": (self.nextEntry, _("play next playlist entry")),
106 "menu": (self.showMenu, _("menu")),
107 "skipListbegin": (self.skip_listbegin, _("jump to listbegin")),
108 "skipListend": (self.skip_listend, _("jump to listend")),
109 "prevBouquet": (self.switchToPlayList, _("switch to playlist")),
110 "nextBouquet": (self.switchToFileList, _("switch to filelist")),
111 "delete": (self.deletePlaylistEntry, _("delete playlist entry")),
112 "shift_stop": (self.clear_playlist, _("clear playlist")),
113 "shift_record": (self.playlist.PlayListShuffle, _("shuffle playlist")),
116 self["InfobarEPGActions"] = HelpableActionMap(self, "InfobarEPGActions",
118 "showEventInfo": (self.showEventInformation, _("show event details")),
121 self["actions"] = MoviePlayerActionMap(self, ["DirectionActions"],
123 "right": self.rightDown,
124 "rightRepeated": self.doNothing,
125 "rightUp": self.rightUp,
126 "left": self.leftDown,
127 "leftRepeated": self.doNothing,
128 "leftUp": self.leftUp,
131 "upRepeated": self.up,
132 "upUp": self.doNothing,
134 "downRepeated": self.down,
135 "downUp": self.doNothing,
138 InfoBarSeek.__init__(self, actionmap = "MediaPlayerSeekActions")
140 self.__event_tracker = ServiceEventTracker(screen=self, eventmap=
142 #iPlayableService.evStart: self.__serviceStarted,
143 #iPlayableService.evSeekableStatusChanged: InfoBarSeek.__seekableStatusChanged,
145 iPlayableService.evEOF: self.__evEOF,
148 self.onClose.append(self.delMPTimer)
149 self.onClose.append(self.__onClose)
151 self.righttimer = False
152 self.rightKeyTimer = eTimer()
153 self.rightKeyTimer.timeout.get().append(self.rightTimerFire)
155 self.lefttimer = False
156 self.leftKeyTimer = eTimer()
157 self.leftKeyTimer.timeout.get().append(self.leftTimerFire)
159 self.infoTimer = eTimer()
160 self.infoTimer.timeout.get().append(self.infoTimerFire)
161 self.infoTimer.start(500)
163 self.currList = "filelist"
165 self.coverArtFileName = ""
167 self.playlistIOInternal = PlaylistIOInternal()
168 list = self.playlistIOInternal.open(resolveFilename(SCOPE_CONFIG, "playlist.e2pls"))
171 self.playlist.addFile(x.ref)
172 self.playlist.updateList()
177 def createSummary(self):
178 return MediaPlayerLCDScreen
181 self.session.openWithCallback(self.exitCB, MessageBox, _("Do you really want to exit?"), timeout=5)
183 def exitCB(self, answer):
185 self.playlistIOInternal.clear()
186 for x in self.playlist.list:
187 self.playlistIOInternal.addService(ServiceReference(x[0]))
188 self.playlistIOInternal.save(resolveFilename(SCOPE_CONFIG, "playlist.e2pls"))
191 def checkSkipShowHideLock(self):
192 self.updatedSeekState()
198 self.session.nav.playService(self.oldService)
200 def delMPTimer(self):
201 del self.rightKeyTimer
202 del self.leftKeyTimer
205 def infoTimerFire(self):
206 currPlay = self.session.nav.getCurrentService()
207 if currPlay is not None:
208 stitle = currPlay.info().getInfoString(iServiceInformation.sTitle)
210 stitle = currPlay.info().getName().split('/')[-1]
212 self.updateMusicInformation( artist = currPlay.info().getInfoString(iServiceInformation.sArtist),
214 album = currPlay.info().getInfoString(iServiceInformation.sAlbum),
215 genre = currPlay.info().getInfoString(iServiceInformation.sGenre),
217 self.updateCoverArtPixmap( currPlay.info().getName() )
219 self.updateMusicInformation()
220 self.updateCoverArtPixmap( "" )
222 def updateMusicInformation(self, artist = "", title = "", album = "", year = "", genre = "", clear = False):
223 self.updateSingleMusicInformation("artist", artist, clear)
224 self.updateSingleMusicInformation("title", title, clear)
225 self.updateSingleMusicInformation("album", album, clear)
226 self.updateSingleMusicInformation("year", year, clear)
227 self.updateSingleMusicInformation("genre", genre, clear)
229 def updateSingleMusicInformation(self, name, info, clear):
230 if info != "" or clear:
231 if self[name].getText() != info:
232 self[name].setText(info)
234 def updateCoverArtPixmap(self, currentServiceName):
235 filename = currentServiceName
236 # The "getName" usually adds something like "MP3 File:" infront of filename
237 # Get rid of this...by finding the first "/"
238 # FIXME: this should be fixed in the servicemp3.cpp handler
239 filename = filename[filename.find("/"):]
240 path = os.path.dirname(filename)
241 pngname = path + "/" + "folder.png"
242 if not os.path.exists(pngname):
243 pngname = resolveFilename(SCOPE_SKIN_IMAGE, "no_coverArt.png")
244 if self.coverArtFileName != pngname:
245 self.coverArtFileName = pngname
246 self["coverArt"].instance.setPixmapFromFile(self.coverArtFileName)
249 self.lefttimer = True
250 self.leftKeyTimer.start(1000)
253 self.righttimer = True
254 self.rightKeyTimer.start(1000)
258 self.leftKeyTimer.stop()
259 self.lefttimer = False
260 self[self.currList].pageUp()
261 self.updateCurrentInfo()
265 self.rightKeyTimer.stop()
266 self.righttimer = False
267 self[self.currList].pageDown()
268 self.updateCurrentInfo()
270 def leftTimerFire(self):
271 self.leftKeyTimer.stop()
272 self.lefttimer = False
273 self.switchToFileList()
275 def rightTimerFire(self):
276 self.rightKeyTimer.stop()
277 self.righttimer = False
278 self.switchToPlayList()
280 def switchToFileList(self):
281 self.currList = "filelist"
282 self.filelist.selectionEnabled(1)
283 self.playlist.selectionEnabled(0)
284 self.updateCurrentInfo()
286 def switchToPlayList(self):
287 if len(self.playlist) != 0:
288 self.currList = "playlist"
289 self.filelist.selectionEnabled(0)
290 self.playlist.selectionEnabled(1)
291 self.updateCurrentInfo()
294 self[self.currList].up()
295 self.updateCurrentInfo()
298 self[self.currList].down()
299 self.updateCurrentInfo()
301 def showAfterSeek(self):
304 def showAfterCuesheetOperation(self):
307 def hideAfterResume(self):
310 # FIXME: maybe this code can be optimized
311 def updateCurrentInfo(self):
313 if self.currList == "filelist":
314 idx = self.filelist.getSelectionIndex()
315 r = self.filelist.list[idx]
322 self.summaries.setText(text,1)
325 if idx < len(self.filelist.list):
326 r = self.filelist.list[idx]
330 self.summaries.setText(text,3)
332 self.summaries.setText(" ",3)
335 if idx < len(self.filelist.list):
336 r = self.filelist.list[idx]
340 self.summaries.setText(text,4)
342 self.summaries.setText(" ",4)
345 if not self.filelist.canDescent():
346 r = self.filelist.getServiceRef()
350 self["currenttext"].setText(os.path.basename(text))
352 if self.currList == "playlist":
353 t = self.playlist.getSelection()
356 #display current selected entry on LCD
358 text = text.split('/')[-1]
359 self.summaries.setText(text,1)
360 self["currenttext"].setText(text)
361 idx = self.playlist.getSelectionIndex()
363 if idx < len(self.playlist):
364 currref = self.playlist.getServiceRefList()[idx]
365 text = currref.getPath()
366 text = text.split('/')[-1]
367 self.summaries.setText(text,3)
369 self.summaries.setText(" ",3)
372 if idx < len(self.playlist):
373 currref = self.playlist.getServiceRefList()[idx]
374 text = currref.getPath()
375 text = text.split('/')[-1]
376 self.summaries.setText(text,4)
378 self.summaries.setText(" ",4)
381 if self.currList == "filelist":
382 if self.filelist.canDescent():
383 self.filelist.descent()
384 self.updateCurrentInfo()
388 if self.currList == "playlist":
389 selection = self["playlist"].getSelection()
390 self.changeEntry(self.playlist.getSelectionIndex())
394 if self.currList == "filelist":
395 if self.filelist.canDescent():
396 menu.append((_("add directory to playlist"), "copydir"))
398 menu.append((_("add files to playlist"), "copyfiles"))
399 menu.append((_("switch to playlist"), "playlist"))
401 menu.append((_("switch to filelist"), "filelist"))
403 menu.append((_("shuffle playlist"), "shuffle"))
405 menu.append((_("delete"), "delete"))
406 menu.append((_("clear playlist"), "clear"))
407 menu.append((_("hide player"), "hide"));
408 menu.append((_("save playlist"), "saveplaylist"));
409 menu.append((_("load playlist"), "loadplaylist"));
410 menu.append((_("delete saved playlist"), "deleteplaylist"));
411 self.session.openWithCallback(self.menuCallback, ChoiceBox, title="", list=menu)
413 def menuCallback(self, choice):
417 if choice[1] == "copydir":
418 self.copyDirectory(self.filelist.getSelection()[0])
419 elif choice[1] == "copyfiles":
421 self.playlist.clear()
422 self.copyDirectory(os.path.dirname(self.filelist.getSelection()[0].getPath()) + "/", recursive = False)
423 self.playServiceRefEntry(self.filelist.getServiceRef())
424 elif choice[1] == "playlist":
425 self.switchToPlayList()
426 elif choice[1] == "filelist":
427 self.switchToFileList()
428 elif choice[1] == "delete":
429 if self.playlist.getSelectionIndex() == self.playlist.getCurrentIndex():
432 elif choice[1] == "clear":
434 self.playlist.clear()
435 self.switchToFileList()
436 elif choice[1] == "hide":
438 elif choice[1] == "saveplaylist":
440 elif choice[1] == "loadplaylist":
442 elif choice[1] == "deleteplaylist":
443 self.delete_saved_playlist()
444 elif choice[1] == "shuffle":
445 self.playlist.PlayListShuffle()
448 def showEventInformation(self):
449 from Screens.EventView import EventViewSimple
450 from ServiceReference import ServiceReference
451 evt = self[self.currList].getCurrentEvent()
453 self.session.open(EventViewSimple, evt, ServiceReference(self.getCurrent()))
455 # also works on filelist (?)
456 def getCurrent(self):
457 return self["playlist"].getCurrent()
459 def deletePlaylistEntry(self):
460 if self.currList == "playlist":
461 if self.playlist.getSelectionIndex() == self.playlist.getCurrentIndex():
465 def skip_listbegin(self):
466 if self.currList == "filelist":
467 self.filelist.moveToIndex(0)
469 self.playlist.moveToIndex(0)
470 self.updateCurrentInfo()
472 def skip_listend(self):
473 if self.currList == "filelist":
474 idx = len(self.filelist.list)
475 self.filelist.moveToIndex(idx - 1)
477 self.playlist.moveToIndex(len(self.playlist)-1)
478 self.updateCurrentInfo()
480 def save_playlist(self):
481 self.session.openWithCallback(self.save_playlist2,InputBox, title=_("Please enter filename (empty = use current date)"),windowTitle = _("Save Playlist"))
483 def save_playlist2(self, name):
487 name = time.strftime("%y%m%d_%H%M%S")
489 self.playlistIOInternal.clear()
490 for x in self.playlist.list:
491 self.playlistIOInternal.addService(ServiceReference(x[0]))
492 self.playlistIOInternal.save(resolveFilename(SCOPE_PLAYLIST) + name)
494 def load_playlist(self):
496 playlistdir = resolveFilename(SCOPE_PLAYLIST)
498 for i in os.listdir(playlistdir):
499 listpath.append((i,playlistdir + i))
501 print "Error while scanning subdirs ",e
502 self.session.openWithCallback(self.PlaylistSelected, ChoiceBox, title=_("Please select a playlist..."), list = listpath)
504 def PlaylistSelected(self,path):
506 self.clear_playlist()
507 self.playlistIOInternal = PlaylistIOInternal()
508 list = self.playlistIOInternal.open(path[1])
511 self.playlist.addFile(x.ref)
512 self.playlist.updateList()
514 def delete_saved_playlist(self):
516 playlistdir = resolveFilename(SCOPE_PLAYLIST)
518 for i in os.listdir(playlistdir):
519 listpath.append((i,playlistdir + i))
521 print "Error while scanning subdirs ",e
522 self.session.openWithCallback(self.DeletePlaylistSelected, ChoiceBox, title=_("Please select a playlist to delete..."), list = listpath)
524 def DeletePlaylistSelected(self,path):
526 self.delname = path[1]
527 self.session.openWithCallback(self.deleteConfirmed, MessageBox, _("Do you really want to delete %s?") % (path[1]))
529 def deleteConfirmed(self, confirmed):
531 os.remove(self.delname)
533 def clear_playlist(self):
535 self.playlist.clear()
536 self.switchToFileList()
538 def copyDirectory(self, directory, recursive = True):
539 print "copyDirectory", directory
540 filelist = FileList(directory, useServiceRef = True, isTop = True)
542 for x in filelist.getFileList():
543 if x[0][1] == True: #isDir
545 self.copyDirectory(x[0][0])
547 self.playlist.addFile(x[0][0])
548 self.playlist.updateList()
551 if self.filelist.getServiceRef().type == 4098: # playlist
552 ServiceRef = self.filelist.getServiceRef()
553 extension = ServiceRef.getPath()[ServiceRef.getPath().rfind('.') + 1:]
554 print "extension:", extension
555 if self.playlistparsers.has_key(extension):
556 playlist = self.playlistparsers[extension]()
557 list = playlist.open(ServiceRef.getPath())
559 self.playlist.addFile(x.ref)
561 self.playlist.addFile(self.filelist.getServiceRef())
562 self.playlist.updateList()
563 if len(self.playlist) == 1:
566 def addPlaylistParser(self, parser, extension):
567 self.playlistparsers[extension] = parser
570 next = self.playlist.getCurrentIndex() + 1
571 if next < len(self.playlist):
572 self.changeEntry(next)
574 def previousEntry(self):
575 next = self.playlist.getCurrentIndex() - 1
577 self.changeEntry(next)
579 def deleteEntry(self):
580 self.playlist.deleteFile(self.playlist.getSelectionIndex())
581 self.playlist.updateList()
582 if len(self.playlist) == 0:
583 self.switchToFileList()
585 def changeEntry(self, index):
586 self.playlist.setCurrentPlaying(index)
589 def playServiceRefEntry(self, serviceref):
590 serviceRefList = self.playlist.getServiceRefList()
591 for count in range(len(serviceRefList)):
592 if serviceRefList[count] == serviceref:
593 self.changeEntry(count)
597 if len(self.playlist.getServiceRefList()):
598 currref = self.playlist.getServiceRefList()[self.playlist.getCurrentIndex()]
599 if self.session.nav.getCurrentlyPlayingServiceReference() is None or currref != self.session.nav.getCurrentlyPlayingServiceReference():
600 self.session.nav.playService(self.playlist.getServiceRefList()[self.playlist.getCurrentIndex()])
601 info = eServiceCenter.getInstance().info(currref)
602 description = info and info.getInfoString(currref, iServiceInformation.sDescription) or ""
603 self["title"].setText(description)
604 # display just playing musik on LCD
605 idx = self.playlist.getCurrentIndex()
606 currref = self.playlist.getServiceRefList()[idx]
607 text = currref.getPath()
608 text = text.split('/')[-1]
610 ext = text[-3:].lower()
612 # FIXME: the information if the service contains video (and we should hide our window) should com from the service instead
613 if ext not in ["mp3", "wav", "ogg"]:
615 self.summaries.setText(text,1)
617 # get the next two entries
619 if idx < len(self.playlist):
620 currref = self.playlist.getServiceRefList()[idx]
621 text = currref.getPath()
622 text = text.split('/')[-1]
623 self.summaries.setText(text,3)
625 self.summaries.setText(" ",3)
628 if idx < len(self.playlist):
629 currref = self.playlist.getServiceRefList()[idx]
630 text = currref.getPath()
631 text = text.split('/')[-1]
632 self.summaries.setText(text,4)
634 self.summaries.setText(" ",4)
636 idx = self.playlist.getCurrentIndex()
637 currref = self.playlist.getServiceRefList()[idx]
638 text = currref.getPath()
639 ext = text[-3:].lower()
640 if ext not in ["mp3", "wav", "ogg"]:
642 self.unPauseService()
644 def updatedSeekState(self):
645 if self.seekstate == self.SEEK_STATE_PAUSE:
646 self.playlist.pauseFile()
647 elif self.seekstate == self.SEEK_STATE_PLAY:
648 self.playlist.playFile()
649 elif self.seekstate in ( self.SEEK_STATE_FF_2X,
650 self.SEEK_STATE_FF_4X,
651 self.SEEK_STATE_FF_8X,
652 self.SEEK_STATE_FF_32X,
653 self.SEEK_STATE_FF_64X,
654 self.SEEK_STATE_FF_128X):
655 self.playlist.forwardFile()
656 elif self.seekstate in ( self.SEEK_STATE_BACK_16X,
657 self.SEEK_STATE_BACK_32X,
658 self.SEEK_STATE_BACK_64X,
659 self.SEEK_STATE_BACK_128X,):
660 self.playlist.rewindFile()
662 def pauseEntry(self):
667 self.playlist.stopFile()
668 self.session.nav.playService(None)
669 self.updateMusicInformation(clear=True)
672 def unPauseService(self):
673 self.setSeekState(self.SEEK_STATE_PLAY)
676 class MediaPlayerLCDScreen(Screen):
678 <screen position="0,0" size="132,64" title="LCD Text">
679 <widget name="text1" position="4,0" size="132,35" font="Regular;16"/>
680 <widget name="text3" position="4,36" size="132,14" font="Regular;10"/>
681 <widget name="text4" position="4,49" size="132,14" font="Regular;10"/>
684 def __init__(self, session, parent):
685 Screen.__init__(self, session)
686 self["text1"] = Label("Mediaplayer")
687 self["text3"] = Label("")
688 self["text4"] = Label("")
690 def setText(self, text, line):
691 print "lcd set text:", text, line
693 if text[-4:] == ".mp3":
696 text = text + textleer*10
698 self["text1"].setText(text)
700 self["text3"].setText(text)
702 self["text4"].setText(text)