For posterity, here's what I got to work:
Step Event (obj_camera, obj_game, obj_player, whatever):
GML:
preferredHeight = 320;
viewRatio = window_get_width()/window_get_height();
calculatedWidth = preferredHeight*viewRatio;
camera_set_view_pos(view_camera[0],obj_player.x-calculatedWidth/2,obj_player.y-preferredHeight/2);
camera_set_view_size(view_camera[0], floor(calculatedWidth), floor(preferredHeight));
Then in Game Options, in Windows >> Graphics (may be different on other OSs idrk):
Allow fullscreen switching,
Allow window resize,
Scaling: Full scale
And finally, in the room you are using:
Enable viewports
Viewport 0: Make sure object following is turned OFF. The code above follows obj_player programmatically. The view will NOT follow an object outside of the room if this setting is enabled. I don't think your camera/viewport properties matter.
This will give you a view that maintains default aspect ratio (1:1) when the window resizes and follows the player outside of the room.
Obviously you can change the preferredHeight to whatever suits your game. You can also prefer a standard width instead if you want the view to expand/contract vertically instead of horizontally when the window is resized. To do this, just set a preferredWidth instead of preferredHeight and calculate the height by dividing the preferredWidth by the viewRatio.
EDIT:
You also need to set viewport 0 to visible in addition to enabling viewports in the room, or include this line of code in your step event:
GML:
view_set_visible(view_camera[0],true);