Welcome!
This is the community forum for my apps Pythonista and Editorial.
For individual support questions, you can also send an email. If you have a very short question or just want to say hello — I'm @olemoritz on Twitter.
how to get last video on iphone?
-
That works perfectly, thank you.
Is there some documentation on video players and objc that you could point me to so that I can get a better understanding of how this all works? -
-
Thank you. I had only been looking at Pythonista 3 documentation and just recently came across the photos module. Photos doesn't appear to support much in the way of videos.
I appreciate all of your help. -
Is there a simple way to add controls to the player such as pause, play, etc.?
-
@frankL try this
from objc_util import * AVPlayerItem=ObjCClass('AVPlayerItem') AVPlayer=ObjCClass('AVPlayer') AVPlayerLayer=ObjCClass('AVPlayerLayer') url = nsurl('assets-library://asset/asset.MOV?id=71F89028-7FA0-4C53-B6AD-6659BC8458D8&ext=MOV') u=ObjCClass('AVURLAsset').alloc().initWithURL_options_(url,None) i=AVPlayerItem.playerItemWithAsset_(u) p=AVPlayer.playerWithPlayerItem_(i) videolayer=AVPlayerLayer.playerLayerWithPlayer_(p) import ui v=ui.View(frame=(0,0,500,500)) V=ObjCInstance(v) videolayer.frame=V.bounds() V.layer().addSublayer_(videolayer) v.present('sheet') p.play() bplay = ui.ButtonItem() bplay.title = 'play' def b_play_action(sender): p.play() bplay.action = b_play_action bpause = ui.ButtonItem() bpause.title = 'pause' def b_pause_action(sender): p.pause() bpause.action = b_pause_action v.right_button_items = (bplay, bpause)
-
@frankL you can even find an example of a slider in the minimal à player Of @JonB
-
Thank you. All that is running correctly now. How can I update the slider to indicate the elapsed time status of the video? The Apple documentation is very difficult to understand.
-
@frankL said:
How can I update the slider to indicate the elapsed time status of the video
from objc_util import * import ui AVPlayerItem=ObjCClass('AVPlayerItem') AVPlayer=ObjCClass('AVPlayer') AVPlayerLayer=ObjCClass('AVPlayerLayer') url = nsurl('assets-library://asset/asset.MOV?id=71F89028-7FA0-4C53-B6AD-6659BC8458D8&ext=MOV') u=ObjCClass('AVURLAsset').alloc().initWithURL_options_(url,None) i=AVPlayerItem.playerItemWithAsset_(u) p=AVPlayer.playerWithPlayerItem_(i) videolayer=AVPlayerLayer.playerLayerWithPlayer_(p) #define cmtime, for seeking import ctypes CMTimeValue=ctypes.c_int64 CMTimeScale=ctypes.c_int32 CMTimeFlags=ctypes.c_uint32 CMTimeEpoch=ctypes.c_int64 class CMTime(Structure): _fields_=[('value',CMTimeValue), ('timescale',CMTimeScale), ('flags',CMTimeFlags), ('epoch',CMTimeEpoch)] def __init__(self,value=0,timescale=1,flags=0,epoch=0): self.value=value self.timescale=timescale self.flags=flags self.epoch=epoch c.CMTimeMakeWithSeconds.argtypes=[ctypes.c_double,ctypes.c_int32] c.CMTimeMakeWithSeconds.restype=CMTime c.CMTimeGetSeconds.argtypes=[CMTime] c.CMTimeGetSeconds.restype=c_double class MyView(ui.View): def __init__(self, *args, **kwargs): ui.View.__init__(self, *args, **kwargs) self.background_color = 'white' V=ObjCInstance(self) videolayer.frame=V.bounds() V.layer().addSublayer_(videolayer) bplay = ui.ButtonItem() bplay.title = 'play' def b_play_action(sender): p.play() bplay.action = b_play_action self.update_interval = 0 bpause = ui.ButtonItem() bpause.title = 'pause' def b_pause_action(sender): p.pause() bpause.action = b_pause_action self.right_button_items = (bplay, bpause) slider=ui.Slider(frame=(0,0,self.width,20)) #sender.superview.name = str(sender.value*duration_sec) slider.action=self.slider_action slider.bring_to_front() self.slider = slider self.add_subview(slider) def update(self): s = p.currentTime().a/p.currentTime().b self.slider.value = s/duration_sec def slider_action(self, sender): self.seek(sender.value*duration_sec) @on_main_thread def seek(self,t): T=c.CMTimeMakeWithSeconds(t,1) p.seekToTime_(T,argtypes=[CMTime],restype=None) v=MyView(frame=(0,0,500,500)) v.present('sheet') duration=i.duration() duration_sec=duration.a/duration.b #print(duration_sec) p.play() v.update_interval = 0.1
-
@frankL try until
v.update_interval = 1/60
-
@frankL Is it what you wanted?
-
I see that your code works but I'm trying to implement it in a subview and I can't figure out how to use the v.update_interval method.
-
To clarify, I have a button in the main view that plays a video that the user selects from the main view. The button action instantiates a subview that runs the video and presents the slider, play, pause and stop buttons. The slider works to move the video playback manually but otherwise the slider doesn't display the elapsed time of the video. Any insight you can give me would be appreciated.
-
@frankL said:
how to use the v.update_interval method.
update_interval is not a method but an attribute setting thé interval in seconds between 2 calls to the update méthod in a subclassed view.
-
@frankL said:
Any insight you can give me would be appreciated
It would be easier to help if you post your script.
-
Here is the code. The function with the view for the video player is "play_action".
import ui import csv import os import objc_util from objc_util import * # def show_list_dialog(items=None, *args, **kwargs): items = items or [] tbl = ui.TableView(**kwargs) tbl.data_source = ui.ListDataSource(items) my_sel = {'value': None} class MyTableViewDelegate (object): def tableview_did_select(self, tableview, section, row): my_sel['value'] = tableview.data_source.items[row] tableview.close() tbl.delegate = MyTableViewDelegate() tbl.present(style='sheet') tbl.wait_modal() return my_sel['value'] # # def open_video_ref(): matrix=[] my_path=os.path. abspath("dance_video_ref.csv") with open(my_path,'r',encoding='utf-8') as reader: reader=csv.DictReader(reader) for row in reader: matrix.append([row['Index'], row['Dance'], row['Instructor'], row['Description'], row['Label'], row['url'], row['level'], row['duration']]) return matrix # # MAIN PROGRAM # # # w, h = ui.get_screen_size() h -= 64 bh = bw = 80 # label height and button width mg = 10 # margin view = ui.View(name='Dance Lesson Videos', bg_color='#D98880', frame=(0, 0, w, h)) dance_instructor_value='Select Instructor(s)' dance_instructor = ui.TextField(frame=(100,15,130,0),border_color='black', border_width=2, text=dance_instructor_value, bordered=True, font=('Arial Rounded MT Bold',15)) dance_instructor.height=30 dance_instructor.width=130 dance_instructor.enabled=False view.add_subview(dance_instructor) # def instructor_action(sender): global dance_matrix matrix = open_video_ref() priority = int(dance_priority.text) dance_matrix = [] for row in matrix: if priority == int(row[6]): dance_matrix.append(row) instructor_list = ['All'] for row in dance_matrix: instructor = row[2] if instructor in instructor_list: continue else: instructor_list.append(instructor) f = (0, 0, 400, 300) try: result = show_list_dialog (instructor_list, frame=f, name='Select an Instructor') dance_instructor.text=result if dance_instructor.text != 'All': new_matrix = [] for row in dance_matrix: if row[2] == dance_instructor.text: new_matrix.append(row) dance_matrix = new_matrix dance_button.enabled=True search_dance.text = 'dance name' except: dance_instructor.text = 'try again' pattern_button.enabled=False pattern_video.text = 'dance pattern' play_button.enabled=False return # instructor_button = ui.Button(frame=(10,10,130,0),border_color='black', border_width=2, corner_radius = 10, tint_color = 'black', background_color='#EAECEE', action=instructor_action, font=('Arial Rounded MT Bold',18)) instructor_button.title='Instrctr' instructor_button.width=80 instructor_button.height=40 view.add_subview(instructor_button) # def priority_action(sender): matrix = open_video_ref() priority_list = [] for row in matrix: priority = row[6] if priority in priority_list: continue else: priority_list.append(priority) f = (0, 0, 400, 300) priority_list.sort() result = show_list_dialog(priority_list, frame=f, name='Select Priority') dance_priority.text=str(result) dance_button.enabled=False dance_instructor.text = 'Select Instructor(s)' search_dance.text = 'dance name' pattern_button.enabled=False pattern_video.text = 'dance pattern' play_button.enabled=False return # priority_button = ui.Button(frame=(240,10,130,0),border_color='black', border_width=2, corner_radius = 10, tint_color = 'black', background_color='#EAECEE', action=priority_action, font=('Arial Rounded MT Bold',18)) priority_button.title='Priority' priority_button.width=80 priority_button.height=40 view.add_subview(priority_button) dance_priority_value='0' dance_priority = ui.TextField(frame=(330,15,130,0),border_color='black', border_width=2, text=dance_priority_value, bordered=True, font=('Arial Rounded MT Bold',15)) dance_priority.height=30 dance_priority.width=30 dance_priority.enabled=False dance_priority.alignment=ui.ALIGN_CENTER view.add_subview(dance_priority) dance_value='dance name' search_dance = ui.TextField(frame=(100,65,130,0),border_color='black', border_width=2, text=dance_value, bordered=True, font=('Arial Rounded MT Bold',15)) search_dance.height=30 search_dance.width=130 search_dance.enabled=False view.add_subview(search_dance) # def dance_action(sender): global dance_matrix dance_list = [] for row in dance_matrix: dance = row [1] if dance in dance_list: continue else: dance_list.append(dance) f = (0, 0, 400, 300) try: result = show_list_dialog(dance_list, frame=f, name='Select a Dance') search_dance.text=result new_matrix = [] for row in dance_matrix: if row[1] == search_dance.text: new_matrix.append(row) dance_matrix = new_matrix pattern_button.enabled=True except: search_dance.text = 'try again' return # dance_button = ui.Button(frame=(10,60,130,30),border_color='black', border_width=2, corner_radius = 10, tint_color = 'black', background_color='#EAECEE', action=dance_action, font=('Arial Rounded MT Bold',18)) dance_button.title='Dance' dance_button.width=80 dance_button.height=40 dance_button.enabled=False view.add_subview(dance_button) # pattern_value='dance pattern' pattern_video = ui.TextView(frame=(100,105,80,0),border_color='black', border_width=2, text=pattern_value, bordered=True, font=('Arial Rounded MT Bold',15)) pattern_video.height=65 pattern_video.width=260 pattern_video.enabled=False view.add_subview(pattern_video) # def pattern_action(sender): global found_row global dance_matrix pattern_list = [] for row in dance_matrix: if row[4] in pattern_list: continue else: pattern_list.append(row[4]) f = (0, 0, 400, 300) result = show_list_dialog(pattern_list, frame=f, name='Select a Pattern') found=False for row in dance_matrix: if row[4] == result: found_row = row found=True break if found: pattern_video.text=found_row[3] dance_instructor.text = found_row[2] play_button.enabled=True else: pattern_video.text='none found' dance_button.enabled=False return # pattern_button = ui.Button(frame=(10,120,130,30),border_color='black', border_width=2, corner_radius = 10, tint_color = 'black', background_color='#EAECEE', action=pattern_action, font=('Arial Rounded MT Bold',18)) pattern_button.title='Pattern' pattern_button.width=80 pattern_button.height=40 pattern_button.enabled=False view.add_subview(pattern_button) # def play_action(sender): n = nsurl(found_row[5]) u=ObjCClass('AVURLAsset'). alloc().initWithURL_options_(n,None) i=AVPlayerItem.playerItemWithAsset_(u) p=AVPlayer.playerWithPlayerItem_(i) videolayer=AVPlayerLayer. playerLayerWithPlayer_(p) v=ui.View(frame=(0,0,500,500)) V=ObjCInstance(v) videolayer.frame=V.bounds() V.layer().addSublayer_(videolayer) v.present('sheet') # def play_video_action(sender): p.play() return # def pause_video_action(sender): p.pause() return # def stop_action(sender): p.pause() seek(0) slider.value = 0 return # def add_spaces(sender): print('space') return # duration_sec = float(found_row[7]) slider=ui.Slider(frame=(0,0,v.width,110), background_color = 'lightblue') @on_main_thread def seek(t): T=c.CMTimeMakeWithSeconds(t,1) p.seekToTime_(T,argtypes=[CMTime], restype=None) # def slider_action(sender): seek(sender.value*duration_sec) slider.action=slider_action v.add_subview(slider) # v.left_button_items = (((ui.ButtonItem(title=' ', action= add_spaces))),(ui.ButtonItem(title='\u23F8',action=pause_video_action)), ((ui.ButtonItem(title=' ', action= add_spaces))),((ui.ButtonItem(title='\u25B6', action= play_video_action))), ((ui.ButtonItem(title=' ', action= add_spaces))),((ui.ButtonItem(title='\u23F9', action= stop_action)))) # p.play() return # play_button = ui.Button(frame=(10,170,130,30),border_color='black', border_width=2, corner_radius = 10, tint_color = 'black', background_color='#EAECEE', action=play_action, font=('Arial Rounded MT Bold',18)) play_button.title='\u25B6' play_button.width=80 play_button.height=40 play_button.enabled=False view.add_subview(play_button) AVPlayerItem=ObjCClass('AVPlayerItem') AVPlayer=ObjCClass('AVPlayer') AVPlayerLayer=ObjCClass('AVPlayerLayer') import photos def pick_asset(): assets = photos.get_assets(media_type='video') asset = photos.pick_asset(assets) phasset=ObjCInstance(asset) asseturl=ObjCClass('AVURLAsset').alloc().initWithURL_options_(phasset.ALAssetURL(),None) return asseturl #define cmtime, for seeking import ctypes CMTimeValue=ctypes.c_int64 CMTimeScale=ctypes.c_int32 CMTimeFlags=ctypes.c_uint32 CMTimeEpoch=ctypes.c_int64 class CMTime(Structure): _fields_=[('value',CMTimeValue), ('timescale',CMTimeScale), ('flags',CMTimeFlags), ('epoch',CMTimeEpoch)] def __init__(self,value=0,timescale=1,flags=0,epoch=0): self.value=value self.timescale=timescale self.flags=flags self.epoch=epoch c.CMTimeMakeWithSeconds.argtypes=[ctypes.c_double,ctypes.c_int32] c.CMTimeMakeWithSeconds.restype=CMTime c.CMTimeGetSeconds.argtypes=[CMTime] c.CMTimeGetSeconds.restype=c_double ############ ######################################### nav_view = ui.NavigationView(view) nav_view.present('sheet') #########################################
-
I could be wrong, but it may be that the
update
method is only called at the top level view, the one that you calledpresent
on. So you would need to set the update interval on the top level view,and then implement anupdate
in that view to callupdate
in cvp's custom view. -
ProbBly the "right_ way to do this is to implement an objcblock that can be passed to the AVPlayer's
addPeriodicTimeObserver_forInterval_queue_using_
. -
JonB, you're over my head on that. If you look at my code, how would I call an update at the top level before the subview is instantiated? And my understanding of objc is very small. I wouldn't know how to implement an objclock to pass to AVPlayer. Suggestions? Thanks.
-
Alternatively, edit your post and add the
```
before and after the code. -
@JonB said:
the update method is only called at the top level view
I don't think so, I have ui.Views that I simulate as buttons to play a gif in their image, and def update works perfectly.