I have an app which loads local files into an avPlayer via a AVMutableComposition, it may have as many as 6 audio and video tracks as part of the composition.
I have a UISlider which is used to adjust the volume of each track in the player.
Below is the code used to update the volume.
- (void)updateVolumeForTake:(Take *)take
{
NSInteger trackID = // method that gets trackID
AVMutableAudioMix *audioMix = self.player.currentItem.audioMix.mutableCopy;
NSMutableArray *inputParameters = audioMix.inputParameters.mutableCopy;
AVMutableAudioMixInputParameters *audioInputParams = [inputParameters filteredArrayUsingPredicate:[NSPredicate predicateWithFormat:kTrackIdPredicate, trackID]].firstObject;
[audioInputParams setVolume:myNewVolumeFloat atTime:myDesiredTime];
audioMix.inputParameters = inputParameters;
AVPlayerItem *playerItem = self.player.currentItem;
playerItem.audioMix = audioMix;
}
This is is currently live in the appStore and has been since iOS6 and has always worked with no issue.
On a device running iOS9, the above no longer works at all. I have looked at the release notes and although there is some mention of AVFoundation, I didn't see anything regarding AVAudioMix.I have googled around and have not found anyone else with this issue.
I also tried creating a new project with nothing but an AVPlayer and UISlider and I saw the same behaviour.
My question is as follows, has anyone else experienced this issue?
Is anyone aware of a known bug related to this?
I have found a solution but unfortunately not an exact cause.
I can't say I fully understand why this fixed my issue but here is the solution and an attempt at explaining why it fixed the issue I was experiencing.
- (void)updateVolumeForTake:(Take *)take
{
AVMutableAudioMix *audioMix = [AVMutableAudioMix audioMix];
NSMutableArray *inputParameters = [self.inputParameters filteredArrayUsingPredicate:[NSPredicate predicateWithFormat:kNotTrackIdPredicate, myTrackID]].mutableCopy;
AVCompositionTrack *track = (AVCompositionTrack *)[self.composition trackWithTrackID:myTrackID];
AVMutableAudioMixInputParameters *audioInputParams = [AVMutableAudioMixInputParameters audioMixInputParametersWithTrack:track];
[audioInputParams setVolume:myDesiredVolume atTime:kCMTimeZero];
[inputParameters addObject:audioInputParams];
audioMix.inputParameters = inputParameters;
AVPlayerItem *playerItem = self.player.currentItem;
playerItem.audioMix = audioMix;
self.inputParameters = inputParameters;
}
As you can see above, I have stopped using mutable copies of my AVAudioMix and its inputParameters and instead created a new AVAudioMix and NSMutableArray for inputParameters. The new inputParameters array is a copy of the existing inputParameters (referenced from a property 'self.inputParameters') minus the track matching the one I wish to change.
Secondly I create a new instance of AVMutableAudioMixInputParameters using the track with which I wish to edit the volume of (previously I was getting reference to the existing params with matching trackID and modifying them). I edit that add it to my new array and make that the audio mix of currentItem.
Again I can't say with any certainty why this fixes it, but it did for me, I wonder if all the mutable copies were not being discovered as different at all when I reassigned the audioMix of the playerItem and thats why I didn't hear any change in the volume. (although this seems doubtful).
Anyway my issue is fixed and I hope this can help anyone who has a similar issue.
Related
I'm trying to create an overlay in ArcGIS that has moving graphics/symbols which are updated by coordinates received from roving devices. I'm able to display a simple symbol initially but cannot get it to move on the map. My test code is
GraphicsOverlay machineOverlay = new GraphicsOverlay();
MainMapView.GraphicsOverlays.Add(machineOverlay);
MapPointBuilder rdLocation = new MapPointBuilder(150.864119200149, -32.3478640837185, SpatialReferences.Wgs84);
SimpleMarkerSymbol sRD1234 = new SimpleMarkerSymbol()
{
Color = System.Drawing.Color.Red,
Size = 10,
Style = SimpleMarkerSymbolStyle.Circle
};
Graphic graphicWithSymbol = new Graphic(rdLocation.ToGeometry(), sRD1234);
machineOverlay.Graphics.Add(graphicWithSymbol);
// here the red circle is displayed correctly on the map
rdLocation.SetXY(150.887115, -32.357600);
rdLocation.ReplaceGeometry(rdLocation.ToGeometry());
// here I expect the red circle to move but it doesn't
Do I need to trigger an event to "re-render" or refresh the overlay, or what do I need to do to get the graphic to move on my map?
There was a similar question here and the answer was "just update the geometry" which is what I'm attempting to do, but with no success.
If there is an entirely different or better approach to moving markers on a map please suggest, I'm just getting started in the ArcGIS runtime.
Thanks
After a lot of searching I replaced one line of code and its now working
//rdLocation.ReplaceGeometry(rdLocation.ToGeometry());
graphicWithSymbol.Geometry = rdLocation.ToGeometry();
It seems I misunderstood the function of ReplaceGeometry(). Any clarification on this would be helpful.
First of all, I'm new to programming in general so I'm kind of assuming there's a simple answer to this question, I just couldn't seem to find it anywhere.
I'm making a simple platformer game with enemies that move toward the player. I used this code in the enemy's script underneath the physics process to get the player position:
player_position = get_parent().get_node("Player").get_position
However, upon the player being queue_freed when health reaches 0, the game crashes immediately and I get a null error due to there being no Player node. How can I work around this?
You could just set $Player.visibility to false instead of freeing, or you could check if the player exists first using get_parent().has_node("Player")
When you destroy the player, the physics process function is still trying to get the player node, even though it doesn't exist. So, as Lucas said, you could replace:
player_position = get_parent().get_node("Player").get_position
with something like...
if get_parent().has_node("Player"):
player_position = get_parent().get_node("Player").get_position
(Before setting player_position, it will check if the player node even exists)
I think you could use weakref (documentation here).
If you declare a weak reference:
var player_ref: WeakRef = null
and store that reference:
func store_player_node():
var player_ref = weakref(get_parent().get_node("Player"))
Then you can access that reference later:
if player_ref.get_ref():
player_position = player_ref.get_ref().get_position
Weakref has advantage over using get_parent().get_node("Player"). Let's imagine the following scenario:
Player dies and its node is removed from the parent node's children.
New node is created with name Player and added to the scene tree at the same place as the dead Player.
get_parent().get_node("Player") will return a node and the code will not crash. However, it will be a new Player node, not the old one, and I think this is usually undesired.
I hope this helps!
As explained here, OffsetSampleProvider can be used in order to play a specific portion of an audio file. Like this:
AudioFileReader AudioReader = new AudioFileReader("x.wav");
OffsetSampleProvider OffsetProvider = New OffsetSampleProvider(AudioReader);
OffsetProvider.SkipOver = TimeSpan.FromSeconds(5);
OffsetProvider.Take = TimeSpan.FromSeconds(8);
myWaveOut.Init(OffsetProvider);
myWaveOut.Play();
The above example will play an audio for 8 seconds, starting at second 5. However, if I want to play it again, it will not play, unless I set the Position property of the AudioFileReader to 0, and re-create a new instance of OffsetSampleProvider from it. So I would like to know if I'm missing something, or this is the way that OffsetSampleProvider should be used (and if it does, do I have to free any resources related to it).
You could copy the code for OffsetSampleProvider and add a Reset method to it. I'd also avoid using SkipOver for performance reasons and just set the CurrentTime of the AudioFileReader to 5 seconds directly before you play.
So, Brad Larson is awesome. I'm using his GPUImage library since he optimized the CGContextCreateImage for video output to instead render straight into OpenGL. Then he rewrote it to be even more amazing, and half the questions are outdated. The other half have the new callbacks, Like this question, but for the life of me, I can't get the video frames callback to not be nil. (the CMSampleBuffer to CIImage functions)
I know I have to "tag the next frame" to be kept in memory, thanks to his blog. I also know I process it (but GPUImageVideo also does that), then I grab from the framebuffer. Still nill.
The capture command that's supposed to auto-filter it into a CGImage, from the CGImagePicture's processImageUpToFilter function seems to be what I want, and I've seen it mentioned, but I am lost as to how to hook up the output to its frameBuffer.
Or should I use GPUImageRawDataOutput, and how to hook up? I've been copying and pasting, editing, experimenting, but unsure if it's just the fact I don't know openGL enough to hook up the right stuff or?
Any help is appreciated. I wouldn't ask, since so many related questions are up here, but I use them and still get nil on the output.
Here is my current try:
func willOutputSampleBuffer(sampleBuffer: CMSampleBuffer!) {
gpuImageVideoCamera.useNextFrameForImageCapture()
//Next line seems like a waste, as this func is called in GPUImageVideoCamera already.
gpuImageVideoCamera.processVideoSampleBuffer(sampleBuffer);
if let image = gpuImageVideoCamera.imageFromCurrentFramebuffer()
{
//it's nil
}
}
It seems to use the filter instead, and useNextFrame should be AFTER processing to not go super-slow.
Inside of willOutputSampleBuffer, this is it.
if let image = transformFilter.imageFromCurrentFramebuffer()
{
// image not nil now.
}
transformFilter.useNextFrameForImageCapture(); //enusre this comes after
This has given us stunning speeds that beat Google's p2p library. Brad, thanks, everyone should support your efforts.
I am trying to update the metadata on the multimedia image in C# using Tridion's TOM.NET API like this
componentMM.LoadXML(localComponent.GetXML(XMLReadFilter.XMLReadALL));
// make changes to the component mm multimedia text;
localComponent.UpdateXML(componentMM.InnerXML);
localComponent.Save(True)
While this works for other components, it is failing for Multimedia images.
<?xml version="1.0"?>
<tcm:Error xmlns:tcm="http://www.tridion.com/ContentManager/5.0"
ErrorCode="80040345" Category="19" Source="Kernel" Severity="2">
<tcm:Line ErrorCode="80040345" Cause="false" MessageID="16137"><![CDATA[
Unable to save Component (tcm:33-32599).
]]><tcm:Token>RESID_4574</tcm:Token>
<tcm:Token>RESID_4418</tcm:Token>
<tcm:Token>tcm:33-32599</tcm:Token>
</tcm:Line>
<tcm:Line ErrorCode="80040345" Cause="true" MessageID="15747"><![CDATA[
Unexpected element: MultimediaFileSize
]]><tcm:Token>MultimediaFileSize</tcm:Token>
</tcm:Line>
<tcm:Details>
<tcm:CallStack>
<tcm:Location>ComponentBL.CheckMultiMediaProperties</tcm:Location>
<tcm:Location>ComponentBL.CheckMultiMediaProperties</tcm:Location>
<tcm:Location>ComponentBL.Update</tcm:Location>
<tcm:Location>XMLState.Save</tcm:Location>
<tcm:Location>Component.Save</tcm:Location>
</tcm:CallStack>
</tcm:Details>
</tcm:Error>
Can you please let me know what am I doing wrong here?
Thanks for your responses. I was deleting the node but at the wrong place. I update the code like this and it works fine now.
if (localComponent.IsMultimediaComponent)
{
XmlNode multimediaFileSizeNode = localComponentXML.SelectSingleNode("//*[local-name()='MultimediaFileSize']",tridionNamespace);
XmlNode dataNode = multimediaFileSizeNode.ParentNode;
dataNode.RemoveChild(multimediaFileSizeNode);
}
localComponent.UpdateXML(localComponentXML.InnerXml);
Include only the tcm:Metadata node in your update?
Specifically, it is complaining about you specifying the size of the mm file, which you shouldn't, that's a system property. Clean up the XML you receive from Tridion to remove that property (it might then complain about another property, just do what it asks you to).
EDIT: Reading the error messages is a great skill to have...
When you do this you need to only save the modified metadata data (not the entire XML). Try removing all of the child nodes except tcm:Metadata from the XML structure before calling .UpdateXML()
Perhaps you could paste your sample XML if you need further assistance.
I usually do this way:-
mComponent = (Component)mTDSE.GetObject("YOUR-COMPONENT-ID", EnumOpenMode.OpenModeView, null, XMLReadFilter.XMLReadAll);
mComponent.CheckOut(false);
mComponent.MetadataFields["YOUR-METADATA-FIELD-NAME"].value[1] = "VALUE TO BE REPLACED";
mComponent.Save(true);