AVPlayer seekToTime not working properly - swift

Im having a problem with seeking with AVPlayer.seekToTime, I have the time index that I want to seek to inside a scrollViewDidScroll method like this:
func scrollViewDidScroll(scrollView: UIScrollView) {
let offsetTime = scrollView.contentOffset.y * 0.1
self.playerController.player?.seekToTime(CMTime(seconds: Double(offsetTime), preferredTimescale: 10), toleranceBefore: kCMTimePositiveInfinity, toleranceAfter: kCMTimeZero)
}
But the video does not flow nice. For example, when you scroll I want the video to only to move forward like 0.01 of a second (the video I have is real short only about 2.0 sec long) but when you scroll far enough, instead the video moves forward almost a whole second. Its really choppy and I'm not sure why I can't seek to like say 1.00 seconds to 1.01 seconds and have the image representing the time index on the player move. Is this possible? What am I doing wrong? Please help!
PS: I never call self.playerController.player?.play() if this helps

Maybe your tolerance before is not set right. try the Following:
instead of your code:
let offsetTime = scrollView.contentOffset.y * 0.1
let seekTime : CMTime = CMTimeMake(Double(offsetTime), 1000)
self.playerController.player?.seekToTime(seekTime, toleranceBefore: kCMTimeZero, toleranceAfter: kCMTimeZero)
the Time Scale tells how many units you have per second
Also, toleranceBefore should ALSO be kCMTimeZero
hope this Helps :-)

Hey I tried the above code in a Xamarin project using Octane.Xam.Videoplayer: https://components.xamarin.com/view/video-player
It worked really well!
Here is my sample code which I put into a Custom Renderer that inherited from VideoPlayerRenderer which can be found in the namespace Octane.Xam.VideoPlayer.iOS.Renderers:
public void CustomSeek(int seekTime)
{
CMTime tm = new CMTime(seekTime, 10000);
if(NativeVideoPlayer.Player != null)
NativeVideoPlayer.Player.Seek(tm, CMTime.Zero, CMTime.Zero);
}
Keep in mind that you will have to use Version 1.1.4 of the Octane.Xam.VideoPlayer to gain access to the NativeVideoPlayer property of the VideoPlayerRenderer. In the future this property may be renamed to PlayerControl.

Related

AudioKit: How to change tempo on the fly?

I am new to AudioKit and programming music app. I'm building a metronome app and using AudioKit's AKMetronome. I want to have a feature where a user can specify a sequence of beat patterns with different tempo. But I find it is inaccurate to use apple's DispatchQueue.
I'm thinking of rewriting the metronome using AKSequencer. Is there a way to use AudioKit's sequencer to change tempo on the fly or generate a sequence with multiple different tempo? (Sequencer example: https://github.com/AudioKit/Cookbook/blob/main/Cookbook/Cookbook/Recipes/Shaker.swift)
metronome.tempo = 120
let first_interval = 60.0 / 120.0
let switchTime1 = DispatchTime.now() + (first_interval * 4.0)
metronome.play()
DispatchQueue.main.asyncAfter(deadline: switchTime1, execute: {
self.metronome.tempo = 200
})
let second_inter = 60.0 / 200.0
let switchTime2 = switchTime1 + (second_inter * 8.0)
DispatchQueue.main.asyncAfter(deadline: switchTime2, execute: {
self.metronome.tempo = 120
})
Update:
I figured out that you can assign a callback function to AKMetronome using AKMetronome.callback. (https://audiokit.io/docs/Classes/AKMetronome.html#/s:8AudioKit11AKMetronomeC8callbackyycvp) You can then update the tempo at the start of a new sequence.
A possible solution would be to create a tempo track, which would contain tempo events that when processed change the sequencer's tempo.
This is an outline of what should be done:
Create a track to contain the tempo events, using AKSequencer's addTrack method. Connect this track to an AKCallbackInstrument. Please see this answer on how to connect an AKCallbackInstrument to an AKSequencer track.
Add the tempo events to the track, at the time positions where there are tempo changes. As far as I know, there are no standard MIDI events for indicating tempo changes (such as a control change for tempo). But as you will be interpreting the events yourself with a callback function, it doesn't really matter what type of event you use. I explain below how to represent the tempo.
Process the events in the callback function and set AKSequencer's tempo to the indicated tempo.
It’s a little difficult to represent the tempo value inside a MIDI event because usually, MIDI parameters go from 0 to 127. What I would do is use a Note On event, in the note's pitch I would store tempo div 128 and in the note's velocity, tempo % 128.
This is what your callback function would look like:
func tempoCallback(status:UInt8, note:MIDINoteNumber, vel:MIDIVelocity) -> () {
guard let status = AKMIDIStatus(byte: status),
let type = status.type,
type == .noteOn else { return }
let tempo: Int = note * 128 + vel
sequencer.tempo = Double(tempo)
}

Using Swift, how do I animate the .setPosition() method of an NSSplitView without visually stretching its contents?

I would like to animate the appearance of a NSSplitViewItem using .setPosition() using Swift, Cocoa and storyboards. My app allows a student to enter a natural deduction proof. When it is not correct, an 'advice view' appears on the right. When it is correct, this advice view will disappear.
The code I'm using is the below, where the first function makes the 'advice' appear, and the second makes it disappear:
func showAdviceView() {
// Our window
let windowSize = view.window?.frame.size.width
// A CGFloat proportion currently held as a constant
let adviceViewProportion = BKPrefConstants.adviceWindowSize
// Position is window size minus the proportion, since
// origin is top left
let newPosition = windowSize! - (windowSize! * adviceViewProportion)
NSAnimationContext.runAnimationGroup { context in
context.allowsImplicitAnimation = true
context.duration = 0.75
splitView.animator().setPosition(newPosition, ofDividerAt: 1)
}
}
func hideAdviceView() {
let windowSize = view.window?.frame.size.width
let newPosition = windowSize!
NSAnimationContext.runAnimationGroup{ context in
context.allowsImplicitAnimation = true
context.duration = 0.75
splitView.animator().setPosition(newPosition, ofDividerAt: 1)
}
}
My problem is that the animation action itself is causing the text in the views to stretch, as you can see in this example: Current behaviour
What I really want is the text itself to maintain all proportions and slide gracefully in the same manner that we see when the user themselves moves the separator: Ideal behaviour (but to be achieved programmatically, not manually)
Thus far in my troubleshooting process, I've tried to animate this outside of NSAnimationContext; played with concurrent drawing and autoresizing of subviews in XCode; and looked generally into Cocoa's animation system (though much of what I've read doesn't seem to have direct application here, but I might well be misunderstanding it). I suspect what's going on is that the .animator() proxy object allows only alpha changes and stretches---redrawing so that text alignment is honoured during the animation might be too non-standard. My feeling is that I need to 'trick' the app into treating the animation as though it's being performed by the user, but I'm not sure how to go about that.
Any tips greatly appreciated...
Cheers

Swift: SKConstraint for scale? (or equivalent) Stuttering

At the minute I'm using the SKConstraint.positionX(rangex, y: rangey) to confine my SKCameraNode within the game board I've created. This is nice because when you hit the boundary there's no stuttering. But my current way to cap the scale of the camera creates a stutter as it hits the bound as it goes past and pings back.
#objc func zoomedView(_ sender:UIPinchGestureRecognizer) {
if newCamera.xScale > 0.148{
let pinch = SKAction.scale(by: 1/sender.scale, duration: 0.0)
newCamera.run(pinch)
sender.scale = 1.0
} else {newCamera.setScale(0.148)}
}
Is there an SKConstraint for scale (or equivalent) which is a better way to stop this stutter? Thanks :)
There is no direct SKConstraint equivalent for scale, however the reason you're experiencing the stuttering is as you go over the bound it snaps back the next time the function is called, rather before a frame is rendered, so theoretically you could zoom in massively instantaneously, and stay there until you activate the zoom function again.
A way to create an equivalent is to put the code checking whether the scale is greater than x in the rendering loop as outlined here.
So if you were to check at the last possible moment:
override func didFinishUpdate() {
if newCamera.xScale < 0.148{
newCamera.setScale(0.148)
} else if newCamera.xScale > 10{
newCamera.setScale(10)
}
}

SKAction Sequencing and Grouping Animations

I'm doing some death animations for a game, and wanted to ask for some help. I want my monster to disappear in a puff of smoke, but not before it animates a slash effect going across his body.
I have 3 animations that I want to use:
weaponSlash - a line that draws across the monster. Looks like you slashed him with a sword.
smoke - a puff of smoke that slowly expands out
monsterFalling - the monster falls back, startled
What I want to do is play it in this order:
Simultaneously, the slash appears & the monster starts to fall back
About 0.25s into the above animation, I want the cloud to start to appear
When the cloud is about to end (so maybe after 1s) I want the monster to disappear
Remove the smoke, the monster, the sword, etc, and drop some coins on the ground
I started like this, as a test that works somewhat: (ignore the above times for now)
//Cancel any current actions, like a monster attacking
monster.removeAllActions()
//since you can't play 3 animations on one node at the same time, you have to create 3 separate nodes for everything
let slash = SKSpriteNode()
let cloud = SKSpriteNode()
cloud.size = monster.size
slash.size = monster.size
monster.addChild(cloud)
monster.addChild(slash)
//Start the slash animation
slash.run(self.player.currentlyEquippedWeapon.attackAnimation())
//Start the cloud animation (how I get it is elsewhere and not relevant)
cloud.run(cloudAnimation)
//Run the monster death animation, followed by the cleanup/coin dropping
monster.run(SKAction.sequence([monster.deathAnimation(), SKAction.wait(forDuration: 1), postDeathActions]))
The variable PostDeathActions above simply removes the monster node and animates some coins falling.
WHERE I NEED SOME HELP
So the above code doesn't work so great in that the animations all run independently of each other. Based on this, you can see how regardless of whether the slash/cloud finish, the monster will run two actions: him falling back, followed by cleanup, which just removes the monster and spawns the coins. As you can see I tried to delay this by adding a 1s delay but this is all somewhat of a hack since I may have different monsters or attacks, etc, that are faster/slower. I'd rather guarantee that everything finishes before I despawn the monster.
I tried to group this into an SKAction.Run like so:
let preDeath = SKAction.run {
[unowned self] in
monster.run(monster.deathAnimation()
slash.run(self.player.currentlyEquippedWeapon.attackAnimation())
cloud.run(cloudAnimation)
}
but this runs everything at the same time again.
What I want to do is sequence it like this (pseudo code):
let preDeathAnimations = SKAction.Group([slash, cloud, monsterDeathAnimation])
])
SKAction.sequence([preDeathAnimations, postDeathActions])
So this way it'll run all 3 before running cleanup.
Is there a way to do something like this? I know Sequnce/Group need to be run against an SKNode, but I don't have 3 separate ones.
Thanks for your time reading this and any advice you can offer!
This is one idea that I had, but you could use threading + state + onCompletion blocks to take the math out of it. I didn't test it out fully but this general concept should work:
let slash = SKAction.fadeIn(withDuration: 0.5)
let fall = SKAction.fadeOut(withDuration: 0.25)
let puff = SKAction.fadeIn(withDuration: 0.1)
// Put in ALL of the actions from ALL parties that you want to happen prior to puff:
func findLongestTime(from actions: [SKAction]) -> TimeInterval {
var longestTime = TimeInterval(0)
for action in actions {
if action.duration > longestTime { longestTime = action.duration }
}
// Note, if you put a sequence into this function I don't know if it will work right..
// Might need another func like `findDurationOfSequence(_ sequence: SKAction) -> TimeInterval
return longestTime
}
// Note, if you have the monster doing more than falling prior to puff, then you will
// need to subtract those as well:
let monsterActionsPriorToPuff = [fall]
// Add the duration of all monster animations prior to puff:
var MAPTP_duration = TimeInterval(0)
for action in monsterActionsPriorToPuff {
MAPTP_duration += action.duration
}
// Calculate our final wait time, with no negative numbers:
var waitTime = findLongestTime(from: [slash, fall]) - MAPTP_duration
if waitTime < 0 { waitTime = 0 }
let wait = SKAction.wait(forDuration: waitTime)
// Our monster sequence (I forgot to add the disappear, just add after puff)
let monsterSequence = SKAction.sequence([fall, wait, puff])
// Player slashes:
SKSpriteNode().run(slash)
// Monster will wait 0.25 seconds after falling,
// for slash to finish before puffing:
SKSpriteNode().run(monsterSequence)
et me know if this idea isn't working I can try updating it.

sceneDidLoad Running Twice

When I run my program. The code I put into "override func sceneDidLoad()" runs two times.
E.g.
Note: I have no idea why this picture is not uploading, but it shows "spawn" happening twice.
This code should only run once when "sceneDidLoad()" is called.
Here is the code for the "sceneDidLoad" function, and for the "testSpawn()" function (which is the specific one that gave the duplicated printout).
class GameScene: SKScene {
var mapTerrain: SKTileMapNode!
override func sceneDidLoad() {
cam = SKCameraNode()
cam.xScale = 1
cam.yScale = 1
//do zoom by change in scale in pinch. (E.g. if they start out 5 units apart and end up 15 units apart, zoom by a factor of 3
self.camera = cam
self.addChild(cam)
cam.position = CGPoint(x: 100, y: 100)
setupLayers()
loadSceneNodes()
setUpUI()
testSpawn()
//print("\(self.frame.width), \(self.frame.height)")
}
func testSpawn(){
let RedLegion = legion(texture: textureRedLegion, moveTo: nil, tag: 1, health: 2)
RedLegion.position = mapTerrain.centerOfTile(atColumn: 0, row: 0)
RedLegion.team = "Red"
unitsLayer.addChild(RedLegion)
legionList.append(RedLegion)
print("spawn")
}
}
Note: Not all of the code is here (like "setUpLayers()"), if needed I can supply it, I just do not think it is neccessary.
Search your whole document for "print("spawn")" just to make sure that is the only time you call the function. Also check for "testSpawn()" to make sure it is only called once. Additionally, instead of relying on this print to count how many times the sceneDidLoad runs, place a print directly within your sceneDidLoad. Finally, check to make sure you are not creating the scene twice.
I've also seen this and submitted a bug report but apple responded saying that it is intended behavior. Apple said that it creates a dummy scene and then creates the actual scene. Before it runs the second time it gets rid of anything done the first time so you shouldn't get any errors from it. The bug is really hard to reproduce, one of my friends working off the same repository that I was but did not experience the bug.
I changed sceneDidLoad to didMoveToView:(SKView *)view if you are looking for a solution to this. Make sure you xcode is up to date.